Как получить LiveData из комнаты?

Я пытаюсь получить LiveData из комнаты. Таким образом, мой RecycleView может иметь обновления в реальном времени, если что-либо в базе данных изменилось.

Я пробовал без LiveData, и это работает, но когда я добавляю LiveData, всегда появляется эта ошибка.

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()

Я гуглил и искал решение на этом сайте, но все с этой проблемой использовали rxjava, rxandroid, rxkotlin или ArrayList. И для них решение — заменить ArrayList на List, а для RX попробовать сопрограмму. Ну, я использую Coroutine и List, и до сих пор нет прогресса.

Это мой КлубДао

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

В клубе у меня есть эти атрибуты

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 должен быть списком, но я конвертирую их в json и обратно с помощью TypeConverters

TypeConverter здесь

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)
    }
}

И моя БД

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()
    }
}

В моем фрагменте мне нужно просмотреть все клубы из базы данных через ViewModel и Repository и отправить их в RecycleView

Теперь я получаю ошибку:

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()

Кто-нибудь знает решение для этого?

Обновлено:

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
}

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'
}

в ClubDao, когда забавный возвращаемый тип имеет LiveData, это не обязательно должно быть suspend забавным.

veritas1 01.07.2019 11:51

@veritas1 при использовании Coroutines с комнатой, не должен ли я использовать приостановку, чтобы он не выполнял запрос в основном потоке?

Uruk Maat Ra 01.07.2019 11:56

Нет, Room обеспечит выполнение запроса в фоновом потоке, если тип возвращаемого значения — LiveData.

veritas1 01.07.2019 12:32

@veritas1 в комнате 2.1 с поддержкой сопрограмм, вы нужно приостанавливаете; вот как фреймворк знает и предоставляет правильный контекст. (источник: medium.com/androiddevelopers/room-coroutines-422b786dc4c5)

Martin Marconcini 01.07.2019 12:37

@UrukMaatRa, какую версию Room и Kotlin вы используете.

Martin Marconcini 01.07.2019 12:39

Можете ли вы опубликовать свои зависимости? Они могут помочь в раскрытии проблемы.

P Fuster 01.07.2019 13:06

@MartinMarconcini: В статье, которую вы цитируете, есть ровно одна ссылка на LiveData, и в ней не говорится, что вам нужно использовать функцию suspend для возврата LiveData. По моим тестам, возврат LiveData в Room 2.1.0 работает так же, как и в предыдущих выпусках — вы не используете suspend. Если у вас есть какой-то другой ресурс, который демонстрирует ваше утверждение, я бы с удовольствием посмотрел на него!

CommonsWare 01.07.2019 13:43

@CommonsWare У меня нет статьи, у меня есть только то, что LiveData не имеет ничего общего с сопрограммами. LiveData — это просто держатель стоимости с учетом жизненного цикла, вот и все. В Room 2.1 добавлена ​​поддержка приостановки курутина. Я не возвращаю LiveData из комнаты, так как есть поддержка сопрограмм. LiveData предназначен для того, чтобы репо открывало модель представления и т. д. (на мой взгляд, то, как LiveData ограничивается решениями Google).

Martin Marconcini 01.07.2019 13:56

«Я не возвращаю LiveData из Room, так как есть поддержка совместной процедуры» — LiveData поддерживается с момента выпуска Room. Вы утверждали, что вы "нужноsuspend". В то время как в этот проект я использую LiveData без suspend. Итак, на чем основано ваше заявленное требование?

CommonsWare 01.07.2019 14:00

Что вы пытаетесь обнаружить? Как LiveData связана с сопрограммой? вы это уже знаете. Поддержка Room Coroutines в версии 2.1+ осуществляется через ключевое слово suspend. В противном случае вы не используете поддержку сопрограмм Room. Все, что LiveData дает вам, — это осведомленность о жизненном цикле для хранения значения, чтобы оно не выдавало что-то, когда вы больше не можете его получать. Я не следил за комнатой больше месяца, все быстро меняется, и теперь он может поддерживать все, но если бы вы знали это, вы бы уже написали ответ. Так что ты пытаешься утверждать? Вы знаете, как решить пост ОП? Отвечай тогда.

Martin Marconcini 01.07.2019 14:06

@MartinMarconcini roomVersion = 2.1.0 coroutinesVersion = 1.2.0 Tnx!

Uruk Maat Ra 01.07.2019 14:41

@PFuster Я добавил свои градации. Спасибо!

Uruk Maat Ra 01.07.2019 14:41

@UrukMaatRa Насколько я понимаю (посмотрев на доступный исходный код) для Room, а что нет, вывод состоит в том, что нет смысла отмечать функцию suspend, если она будет возвращать LiveData. Что вы ожидаете, так это то, что если вы это сделаете, и это сработает, то сопрограмма отправит LiveData, когда это позволит жизненный цикл, но это много работы и потребует, чтобы ваш код репо/БД был осведомлен о контексте/жизненном цикле, что, как я понимаю, не является намерением Google. Все проекты Room+Coroutine, которые я вижу, не возвращают LiveData при приостановке.

Martin Marconcini 01.07.2019 17:19

@MartinMarconcini Я получил идею из этого «Понимание сопрограмм Kotlin на Android (Google I / O'19)» - они используют LiveDate в Dao, а затем emitSource вместо youtu.be/BOHK_w09pVA?t=1272

Uruk Maat Ra 01.07.2019 17:36

@UrukMaatRa, но код там не тот, что вы написали. Код, который они там демонстрируют, использует liveDataфункция и не содержит suspend (поскольку функция liveData изначально является приостанавливающей сопрограммой). Мы все в этом разделе комментариев на одной странице о том, что такое LiveDataliveData{} и suspend/сопрограммы? Я думаю, что мы смешиваем вещи здесь. Я позволю другим экспертам объяснить это, потому что ясно, что я не улучшаю ситуацию. Удачи.

Martin Marconcini 01.07.2019 20:24
2
15
5 984
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Как упоминалось в комментариях, удалите suspend. Когда метод возвращает наблюдаемое, нет причин приостанавливать его работу, поскольку он просто возвращает объект и не выполняет никаких запросов до тех пор, пока он не будет обнаружен.

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

Интеграция сопрограмм Room дает возможность возвращать значения приостановки, но когда само значение неактивно, нет причин его использовать.

Я нашел хорошее решение проблемы с использованием rxJava с room и livedata. Этот подход аналогичен использованию Retrofit с RxJava с использованием LiveDataReactiveStreams, который по умолчанию использует текучий.

В вашем подходе я бы рекомендовал:

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

изменился на

ДАО:

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

Репозиторий:

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
   })
}

затем вы можете наблюдать за объектом livedata, как обычно, в своем представлении.

Надеюсь, то, что я написал, понятно, если нет, я постараюсь сделать это лучше :)

Другие вопросы по теме