With an awesome I/O17 being over, one of the big announcements were official support for Kotlin.
Realm is already fully interoperable with Kotlin and we have a Kotlin example here and here that demonstrates how it works.
But we want to make that support even better and In order to do that, we are looking for feedback on what you would most like to see changed so Realm works even better with Kotlin
We have a few ideas ourselves:
1) (DONE): Add extension functions for all classes with methods that currently accept Class<? extends RealmModel> so KClass can be used directly, e.g. realm.where(Person::class) instead of realm.where(Person::class.java).
2) (DONE) Detect nullability automatically in model classes. Right now you are required to use the Realm @Required annotation, but we can add support for JetBrains @NotNull as well which is how the Kotlin bytecode indicate not-null values. Example var name : String instead of of @Required var name : String
3) (DONE) Annotate our public API with JSR305 annotations in a similar way to Okio/OKHttp. This means that Realm will expose correctly the nullability of return values and input paramaters. For example: val result : RealmResults<Person> = realm.where(Person::class).findAll() instead of val results : RealmResults<Person>? = realm.where(Person::class).findAll(). Also being tracked here: https://github.com/realm/realm-java/issues/4643
But we are very interested what problems _you_ are running into when using Realm from Kotlin.
馃帄 Kotlin!!! 馃帄
When you say problems you are having with Realm using Kotlin, I do not have many problems. realm-java works really well with Kotlin at the moment. I have a few apps that are 100% Kotlin and Realm code bases. Works great.
My vote for priority is your list item #2. Here is an example of a Realm model I create in a Kotlin app using Kotlin nullability data type in them for optional fields:
open class FooModel(@PrimaryKey override var realm_id: Int = 0,
@SerializedName("id") override var api_id: Int = 0,
var name: String? = null,
var created_at: Date = Date(),
var updated_at: Date = Date(),
var position: Int? = null,
var bar: BarModel? = null): RealmObject()
So not needing annotations and instead using the data types of my Kotlin properties would be awesome.
Was there a way to support a non-null initializing value (like "empty RealmResults") for a @LinkingObjects?
It would be relatively easy if we throw on creating queries, basically just creating an empty stub object, but if queries should be allowed it gets enormously more tricky.
I think we can do something for async/await (coroutine). It's still experimental though.
@zaki50 Got anything specific in mind?
@cmelchior Sorry, no.
Maybe async queries could use Kotlin coroutines.
kotlin
/**
<ul>
<li>Use [Realm.where] with [KClass]es.<br />
*/<br />
fun <E : RealmModel> Realm.where(clazz: KClass<E>): RealmQuery<E> {<br />
return where(clazz.java)<br />
}<br />
RealmQuery conditions:kotlin
/**
<ul>
<li>Group conditions on a [RealmQuery].<br />
*/<br />
fun <E : RealmModel> RealmQuery<E>.group(body: () -> Unit) {<br />
beginGroup()<br />
body()<br />
endGroup()<br />
}<br />
LiveData support:
/**
* Map [RealmResults] to a [LiveData].
*/
fun <E : RealmModel> RealmResults<E>.toLiveData(): LiveData<RealmResults<E>> {
val liveData = MutableLiveData<RealmResults<E>>()
addChangeListener{ results ->
liveData.value = results
}
return liveData
}
/**
* Map [RealmObject] to a [LiveData].
*/
fun <E : RealmObject> E.toLiveData(): LiveData<E> {
val liveData: MutableLiveData<E> = MutableLiveData()
this.addChangeListener<E> {
result -> liveData.value = result
}
return liveData
}
@heinrichreimer
why not something like
/**
* Group conditions on a [RealmQuery].
*/
fun <E : RealmModel> RealmQuery<E>.group(body: (RealmQuery<E>) -> Unit) { // <-- changed here
beginGroup()
body()
endGroup()
}
I'm not sure about Kotlin generic syntax, but you get the idea.
@Zhuinden You're right! But it would be even better if the higher order function (body) passed to the group would be an extension function on the RealmQuery itself.
/**
* Group conditions on a [RealmQuery].
*/
fun <E : RealmModel> RealmQuery<E>.group(body: RealmQuery<E>.() -> Unit) {
beginGroup()
body()
endGroup()
}
Another Kotlin fun fact:
Long::class.java actually returns a Class reference to long not Long, this is really easy mistaken when writing migrations:
realm?.schema?.create("Person")
?.addField("id", Long::class.java) // Non-nullable
// Instead we should document that people need to use these instead
.addField("id", Long::class.javaObjectType) // Nullable
.addField("id", Long::class.javaPrimitiveType) // Non-nullable
Even better would be supporting the KClass equivalents directly:
.addField("id", Long::class) // Non-nullable
.addField("id", Long::class).setNullable("id") // Being nullable is hard in Kotlin
@cmelchior I wonder if there would be something like Long?::class in Kotlin...
@zaki50
Maybe async queries could use Kotlin coroutines.
@cmelchior
@zaki50 Got anything specific in mind?
Perhaps a Realm context with a Continuation that exposes the Realm thread to work with objects "across" threads.
Adding a method similar to executeTransaction() but it can have return value and this becomes Realm.
fun <T> Realm.callTransaction(transaction: Realm.() -> T): T {
val ref = AtomicReference<T>()
executeTransaction {
ref.set(transaction(it))
}
return ref.get()
}
We can write a transaction like:
val (group, artifact) = realm.callTransaction {
val group = createObject(Group::class)
group.name = "foo"
val artifact = createObject(Artifact::class)
artifact.name = "bar"
Pair(group, artifact)
}
group.name == "foo"
val (group, subGroup, artifact) = realm.callTransaction {
val group = createObject(Group::class)
group.name = "foo"
val subGroup = createObject(SubGroup::class)
subGroup.name = "bar"
val artifact = createObject(Artifact::class)
artifact.name = "baz"
Triple(group, subGroup, artifact)
}
artifact.name == "baz"
Adding static methods in RealmObject as extension functions to RealmModel like
fun RealmModel.deleteFromRealm() {
RealmObject.deleteFromRealm(this)
}
@RealmClass
open class Foo(
var name: String? = null
) : RealmModel
f: Foo = realm.where(Foo::class).findFirst()
f.deleteFromRealm()
Supporting type-safe query.
fun <T : RealmModel> RealmQuery<T>.equalTo(property: KMutableProperty1<T, out String?>,
value: String, case: Case = Case.SENSITIVE): RealmQuery<T> {
return this.equalTo(property.name, value, case)
}
fun <T : RealmModel> RealmQuery<T>.equalTo(property: KMutableProperty1<T, out Int?>,
value: Int): RealmQuery<T> {
return this.equalTo(property.name, value)
}
...
@RealmClass
open class Foo(
var name: String? = null,
) : RealmModel
@RealmClass
open class Bar(
var name: String? = null,
) : RealmModel
realm.where(Foo::class).equalTo(Foo::name, "abc") // OK
realm.where(Foo::class).equalTo(Foo::name, 0) // compile error (name field is not Int
realm.where(Foo::class).equalTo(Bar::name, "abc") // compile error(Model classes are different
Type-safe queries are a nice idea, but right now it would require a dependency to kotlin-reflect which afaik pulls in ~10K extra methods.
Maybe it would be worth it to pull out Kotlin support in a separate dependency. Then developers could decide on their own if they need type safety.
use reified when defining extension functions that take Class parameter.
inline fun <reified T : RealmModel> Realm.where(): RealmQuery<T> {
return this.where(T::class.java)
}
realm.where<User>().equalTo(...)
@cmelchior
Type-safe queries are a nice idea, but right now it would require a dependency to kotlin-reflect which afaik pulls in ~10K extra methods.
I was thinking so, but it seems to be working without kotlin-reflect dependency.
I tested it with https://github.com/zaki50/GoogleRepositoryChecker/
@zaki50 @cmelchior
I was thinking so, but it seems to be working without kotlin-reflect dependency.
Right, it's not using reflection. I thought this SO answer was a great explanation, helped me grok it a bit better. https://stackoverflow.com/questions/45949584/what-does-the-reified-keyword-in-kotlin-really-do
I wonder if Type Safe builders could be used in some creative way for complex queries
val peopleWithPuppies = realm.where<Person>() { dog { age { lessThan(2) }}
// given
@RealmClass
open class Person : RealmModel {
@PrimaryKey var id=""
var name =""
var dog: Dog? = null
}
@RealmClass
open class Dog : RealmModel {
@PrimaryKey var id=""
var name = ""
var age = 0
}
// Where that query would equate to
realm.where<Person>().lessThan("dog.age", 2)
Not super elegant, but it is type safe. :-)
In Kotlin in is a keyword, so there should be a way to call in(String fieldName, String[] values) with another method name. I would suggest:
/**
* Map Realm's [RealmQuery.in] methods to `oneOf` to bypass using Kotlin's `in` keyword
* as a method name.
*/
fun <E : RealmModel> RealmQuery<E>.oneOf(fieldName: String,
values: Array<String>): RealmQuery<E> = `in`(fieldName, values)
@heinrichreimer I wrote the same code in #4684 ! https://github.com/realm/realm-java/pull/4684/files#diff-ffacb4808566a8a610dec8f6cfacc93fR72
@heinrichreimer the future says that the function we mentioned should have inline modifier for better performance :smile:
Most helpful comment
Another Kotlin fun fact:
Long::class.javaactually returns a Class reference tolongnotLong, this is really easy mistaken when writing migrations:Even better would be supporting the KClass equivalents directly: