realm-kotlin
realm-kotlin copied to clipboard
Support projections
Similar to the ideas expressed here: https://github.com/realm/realm-java/issues/5426 and as exposed by Cocoa here: https://github.com/realm/realm-swift/pull/7375
Projections would decouple the model classes (Database Entity) from how the data is being used in the UI (View Model classes). Which would solve a few use cases
-
Reduce the coupling between layers. A common criticism is that Realm is spreading throughout the entire app as you use the model classes.
-
More type-safe API as projections would only expose data actually needed.
-
Data classes could be used as projection classes as the data is read-only.
-
Preload all data on a background thread, instead of relying on lazy-loading. As the expectation is that all projection fields are being read, we could also bulk-fetch these as an optimization. Something that isn't really possible with our model classes.
We would need to iterate on the exact API and functionality, but something like this:
public class Child: RealmObject {
var name: String
}
public class Dog: RealmObject {
var name: String = ""
var age: Int = 0
var owner: Child? = null
}
// Decoupled projection classes
@Projection(Dog::class) // Required so we can validate fields at compile time
data class DogData1(
val name: String
)
@Projection(Dog::class)
data class DogData2(
val name: String,
@FieldProjection("owner.name") // Collect linked properties
val owner: String
)
// Cocoa has "live" projection classes on which you can attach change listeners. Unclear if that fits Kotlin, but could look like this:
@Projection(Dog::class)
data class DogData: RealmProjection(
val: name: String
)
// Queries should probably still be on model classes, but could be mapped directly to projections.
realm.query<Dog>("name BEGINWITH 'Fido'").asFlow(DogData1::class).collect { data: DogData1 -> updateView(data) }
}
// They could also be used to update in the Realm
val userData = DogData(name = "newName")
realm.write {
find("name = 'Fido'")!!.updateWith(userData)
}
Would generating the projections from the entity classes allow to have pure data classes without realm dependencies? I'm currently doing this manually. The biggest problem is using flows instead of RealmLists which behave differently when comparing equality of data classes. Maybe I need a custom class that implements the List interface using flows...
That is one of the things we want to explore. It would be possible, but would result in some runtime overhead as we would need to calculate/validate that we can actually copy data correctly.
Would even adding a single annotation to the data class be too much, ie.?
@Projection(Dog::Class)
data class DogViewModel(val name: String, val age: Int)
Can you also elaborate on The biggest problem is using flows instead of RealmLists which behave differently when comparing equality of data classes. What exactly are you trying to achieve?
I want to be able to keep observing changes on oneToMany relations in my pure data classes which are generated from the realm classes. So I convert the RealmList to a Flow and then to a StateFlow which I then keep in my data class. However when comparing data classes for equality List<T> behaves different to StateFlow<List<T>>. With lists its actual content is compared while StateFlows are compared by instance (like arrays which generate IDE warnings when used in data classes for that reason). I think the best way to solve that is to use a wrapper around StateFlows similiar to RealmList and comparing that wrapper compares the latest list values.
Personally I don't mind having @Projection as an annotation but when trying to clean the ui from a realm dependency does it matter to have just an annotation as a dependency or classes or whatever?