Search code examples
genericskotlinrealm

Why is my Realm Kotlin Generics workaround not working as expected?


I know that Realm doesn't yet support Generics directly. I have been trying to come up with a workaround as it will reduce duplicate code immensely in my project. (I have many repos which extend base repo and a lot of functions which only differ by RealmObject extended type)

I have got a reasonably good solution working:

open class BaseRepository(private val realm: Realm) {

    fun <T : RealmModel> get(ofType: Class<T>, id: Long): T? {
        return realm.where(ofType).equalTo("id", id).findFirst()
    }  
}

This can be called by my repositories which inherit from BaseRepository like so:

customRepository.get(CustomType::class.java, id)

I tried to improve this further by passing the class type in the constructor instead of the function param:

open class BaseRepository<T : RealmModel?>(private val realm: Realm, private val ofType: Class<T>) {

    fun <T : RealmModel> get(id: Long): T? {
        return realm.where(ofType).equalTo("id", id).findFirst()
    }
}

The above has the following compile time error:

Type mismatch.
Required: T#1 (type parameter of com.acme.data.repository.BaseRepository.get)?
Found:    T#2 (type parameter of com.acme.data.repository.BaseRepository)?

I have made various attempts to get this working but as yet I am unsuccessful.

Questions:

  1. Why can't I pass the ofType: Class in the constructor param but I can in the function param?

  2. Does anyone know how to fix it?

  3. Is there any issue, Realm or otherwise, with what I am doing as it does seem to work and all my tests still pass?

  4. Has anyone got a better solution for this, for example using kotlin extension methods?

Thanks, Paul.


Solution

  • Actually, if you pass the class to the superclass, then you no longer need to use template parameter for your function.

    open class BaseRepository<T : RealmModel>(private val realm: Realm, private val ofType: Class<T>) {
    
        fun get(id: Long): T? =
            realm.where(ofType).equalTo("id", id).findFirst()
    }
    

    An interesting tidbit is that with Kotlin, you could reduce the ::class.java using inline <reified T>.

    open class BaseRepository(private val realm: Realm) {
    
        inline fun <reified T : RealmModel> get(id: Long): T? =
            realm.where(T::class.java).equalTo("id", id).findFirst()
    }
    

    Or if you are using a newer version of Realm (4.3.1+), then you could change this to

    import io.realm.kotlin.where
    
    open class BaseRepository(private val realm: Realm) {
    
        inline fun <reified T : RealmModel> get(id: Long): T? =
            realm.where<T>().equalTo("id", id).findFirst()
    }
    

    I'm just throwing ideas at you but you could technically make this into an extension function

    inline fun <reified T : RealmModel> Realm.get(id: Long): T? =
        where<T>().equalTo("id", id).findFirst()
    

    Now you could do

    val dog = realm.get<Dog>(12L)