3

I'm trying to get LiveData from Room. So my RecycleView can have Live updates if anything in database is changed

I have tried with out LiveData and that works, but when i add LiveData that always shows this error.

error: Not sure how to convert a Cursor to this method's return type     (androidx.lifecycle.LiveData<java.util.List<com.example.models.Club>>).
public abstract java.lang.Object     getAll(@org.jetbrains.annotations.NotNull()

I googled and looked on this site for solution, but every on with this issue have used rxjava, rxandroid, rxkotlin or ArrayList. And for them solution is to replace ArrayList with List, and for RX to try coroutine. Well i am using Coroutine and List and still no progress.

This is my ClubDao

ClubDao
@Query("SELECT * FROM club")
suspend fun getAll(): LiveData<List<Club>>

In Club i have this attributes

Club
@Entity
data class Club(@PrimaryKey var id: Int,
            @ColumnInfo(name = "logo_url") var logoUrl: String,
            @ColumnInfo(name = "name") var name: String,
            @ColumnInfo(name = "town") var town: String,
            @ColumnInfo(name = "address") var address: String,
            @ColumnInfo(name = "contact_name") var contactName: String,
            @ColumnInfo(name = "phone_numbers") var phoneNumbers: String,
            @ColumnInfo(name = "email") var email: String)

phoneNumbers should be List, but i convert those to and from json with TypeConverters

TypeConverter is here

TypeConverter
class ConvertersDB {
    @TypeConverter
    fun fromString(value: String): ArrayList<String> {
        val listType = object : TypeToken<ArrayList<String>>() {

        }.type
        return Gson().fromJson(value, listType)
    }

    @TypeConverter
    fun fromArrayList(list: ArrayList<String>): String {
        val gson = Gson()
        return gson.toJson(list)
    }
}

And my DB

DataBase
@Database(entities = [Club::class], version = 1, exportSchema = false)
@TypeConverters(ConvertersDB::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun clubDao(): ClubDao

    companion object {
        @Volatile
        private var instance: AppDatabase? = null
        private val LOCK = Any()

        operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
            instance ?: buildDatabase(context).also { instance = it }
        }

        private fun buildDatabase(context: Context) = Room.databaseBuilder(context,
                AppDatabase::class.java, "pss.db")
                .allowMainThreadQueries()
                .build()
    }
}

In my fragment i need to observe all the clubs from database via ViewModel and Repository and send them to RecycleView

Now i am getting error:

error: Not sure how to convert a Cursor to this method's return type     (androidx.lifecycle.LiveData<java.util.List<com.example.models.Club>>).
public abstract java.lang.Object     getAll(@org.jetbrains.annotations.NotNull()

Does anyone know solution for this?

EDIT:

Gradle
apply plugin: 'com.android.application'
apply plugin: 'androidx.navigation.safeargs'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'


android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.overswayit.plesnisavezsrbije"
        minSdkVersion 24
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    dataBinding {
        enabled true
    }
    packagingOptions {
        exclude 'META-INF/atomicfu.kotlin_module'
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.31"
    implementation 'com.jakewharton:butterknife:10.1.0'
    implementation 'com.google.android.material:material:1.1.0-alpha07'
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'

    // Room components
    implementation "androidx.room:room-runtime:$rootProject.roomVersion"
    implementation "androidx.room:room-ktx:$rootProject.roomVersion"
    kapt "androidx.room:room-compiler:$rootProject.roomVersion"
    androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"

    // Room and RxJava
    implementation "androidx.room:room-rxjava2:$rootProject.roomVersion"

    // Lifecycle components
    implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.archLifecycleVersion"
    kapt "androidx.lifecycle:lifecycle-compiler:$rootProject.archLifecycleVersion"
    androidTestImplementation "androidx.arch.core:core-testing:$rootProject.androidxArchVersion"

    // ViewModel Kotlin support
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.archLifecycleVersion"

    //LiveData Kotlin
    implementation "androidx.lifecycle:lifecycle-livedata:$rootProject.archLifecycleVersion"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$rootProject.archLifecycleVersion"

    implementation "androidx.lifecycle:lifecycle-runtime-ktx:$rootProject.archLifecycleVersion"

    // Coroutines
    api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$rootProject.coroutines"
    api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.coroutines"

    //RxJava
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    implementation "io.reactivex.rxjava2:rxjava:2.2.6"
    implementation 'com.jakewharton.rxbinding3:rxbinding:3.0.0-alpha2'

    implementation "org.jetbrains.kotlin:kotlin-stdlib:1.3.31"
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
    implementation 'com.squareup.picasso:picasso:2.71828'
    implementation 'com.kaopiz:kprogresshud:1.0.5'
    implementation 'com.squareup:otto:1.3.8'
    implementation 'agency.tango.android:avatar-view:0.0.2'
    implementation 'agency.tango.android:avatar-view-picasso:0.0.2'
    implementation 'com.mikhaellopez:circularimageview:3.2.0'
    implementation 'com.googlecode.libphonenumber:libphonenumber:8.1.0'

    // Data Binding
    kapt "com.android.databinding:compiler:3.1.4"

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation project(path: ':tuple')

    // Navigation Component
    implementation 'androidx.navigation:navigation-fragment-ktx:2.0.0'
    implementation 'androidx.navigation:navigation-ui-ktx:2.0.0'

    //Gson
    implementation 'com.google.code.gson:gson:2.8.5'
}

kapt {
    generateStubs = true
}

Top Level Gradle

buildscript {

    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.31"
        classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0"


        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

ext {
    roomVersion = '2.1.0'
    archLifecycleVersion = '2.2.0-alpha01'
    androidxArchVersion = '2.0.0'
    coroutines = '1.2.0'
}
Daniel Bejan
  • 1,468
  • 1
  • 15
  • 39
Uruk Maat Ra
  • 145
  • 1
  • 12
  • 2
    in `ClubDao`, when the fun return type is of `LiveData` it does not need to be a `suspend` fun. – veritas1 Jul 01 '19 at 09:51
  • @veritas1 when using Coroutines with room, shouldn't i use suspend so it won't do query in main thread ? – Uruk Maat Ra Jul 01 '19 at 09:56
  • No, Room will ensure query is executed on background thread for you when return type is `LiveData`. – veritas1 Jul 01 '19 at 10:32
  • @veritas1 in Room 2.1 with support for co-routines, you *need* the suspend; that's how the framework knows and provides the correct context. (source: https://medium.com/androiddevelopers/room-coroutines-422b786dc4c5) – Martin Marconcini Jul 01 '19 at 10:37
  • @UrukMaatRa what version of Room and Kotlin are you using. – Martin Marconcini Jul 01 '19 at 10:39
  • Can you post your dependencies? They might help in revealing the issue. – P Fuster Jul 01 '19 at 11:06
  • @MartinMarconcini: The article that you cite has exactly one reference to `LiveData`, and it does not say that you need to use a `suspend` function to return `LiveData`. From my tests, returning `LiveData` on Room 2.1.0 works the same as it did in previous releases -- you do not use `suspend`. If you have some other resource that demonstrates your claim, I would love to look at it! – CommonsWare Jul 01 '19 at 11:43
  • @CommonsWare I don't have an article, what I have is that LiveData has nothing to do with Coroutines. LiveData is just a Lifecycle-aware-value-holder, that's it. Support for Couroutine suspension was added in Room 2.1. I don't return LiveData from Room though since there's co-routine support. LiveData is for the repo to expose to the viewmodel and so forth (in my opinion, the way LiveData is constrained by Google's decisions). – Martin Marconcini Jul 01 '19 at 11:56
  • "I don't return LiveData from Room though since there's co-routine support" -- `LiveData` has been supported since Room was released. [You claimed that you "*need* the `suspend`"](https://stackoverflow.com/questions/56833407/how-to-get-livedata-from-room?noredirect=1#comment100220430_56833407). Whereas in [this project](https://gitlab.com/commonsguy/cw-jetpack-kotlin/tree/master/Bookmarker), I use `LiveData` without `suspend`. So, what is the basis for your claimed requirement? – CommonsWare Jul 01 '19 at 12:00
  • What are you trying to discover? How is LiveData related to a coroutine? you already know this. Support for Room Coroutines in 2.1+ is via the `suspend` keyword. Otherwise you're not using Room's coroutine support. All LiveData is giving you, is lifecycle-awareness to hold a value so it won't emit something when you can no longer receive it. I haven't followed room for over a month, things change fast, and it may now support everything, but if you knew this, you'd have already written an answer. So what are you trying to assert? Do you know how to solve the OP's post? Answer it then. – Martin Marconcini Jul 01 '19 at 12:06
  • @MartinMarconcini roomVersion = 2.1.0 coroutinesVersion = 1.2.0 Tnx! – Uruk Maat Ra Jul 01 '19 at 12:41
  • @PFuster I have added my gradle's. Tnx! – Uruk Maat Ra Jul 01 '19 at 12:41
  • @UrukMaatRa As far as I understand (by looking at the available source code) for Room and what not, the conclusion is that there's no point in marking a function `suspend` if it will return LiveData. What you'd expect is that if you do it and it works, then the coroutine will dispatch the LiveData when the lifecycle allows it too, but that's a lot of work and would require your Repo/DB code to be Context/Lifecycle aware, which I understand is not Google's intention. All the Room+Coroutine projects I see, don't return LiveData *when suspending*. – Martin Marconcini Jul 01 '19 at 15:19
  • @MartinMarconcini I got idea from this "Understand Kotlin Coroutines on Android (Google I/O'19)" - They use LiveDate in Dao and then emitSource instead of emit https://youtu.be/BOHK_w09pVA?t=1272 – Uruk Maat Ra Jul 01 '19 at 15:36
  • @UrukMaatRa but the code there is not the same you wrote. The code they demonstrate there is using the `liveData` *function* and doesn't have `suspend` in it (because the liveData function is a suspending coroutine to begin with). Are we all in this comment section on the same page about what `LiveData` `liveData{}` and `suspend`/coroutines are? I think we're mixing things here. I'm going to let the other experts explain it, because clearly I'm not making things better. Good luck. – Martin Marconcini Jul 01 '19 at 18:24

2 Answers2

9

As mentioned in the comments, remove suspend. When a method returns an observable, there is no reason to make it suspend since it just returns and object, does not run any query until it is observed.

@Query("SELECT * FROM club")
fun getAll(): LiveData<List<Club>>

Room's coroutines integration brings ability to return suspend values but when the value itself is asnyc, there is no reason to use it.

yigit
  • 37,683
  • 13
  • 72
  • 58
1

I found a good solution to the problem with using rxJava with room and livedata. The approach is similar to using Retrofit with RxJava, using LiveDataReactiveStreams, which by default uses flowable.

In your approach i would recomend is:

@Query("SELECT * FROM club")
suspend fun getAll(): LiveData<List<Club>>

changed to

DAO:

@Query("SELECT * FROM club")
fun getAll(): (Any rxJava object) Flowable<List<Club>>

Repository:

fun getAllClubs() : LiveData<List<Club>>{
   return LiveDataReactiveStreams.fromPublisher(dao.getAll()"do any transformation or 
   other functionality you want and then if using Maybe for example transform to 
   flowable by .toFlowable()")
}

ViewModel:

val clubList : MediatorLiveData<List<Club>>()

fun setClubData(){
   clubList.addSource(repository.getAllClubs(), {
   clubList.value = it
   })
}

then you can observe the livedata object as usual in your view.

Hope what i wrote is understandable, if not i will try to make it better :)

IfChyy
  • 11
  • 3