Home > database >  Kotlin Coroutines - cannot return object from room db
Kotlin Coroutines - cannot return object from room db

Time:05-28

I'm not super sure what I'm doing here so go easy on me:

Trying to retrieve a string from a pre-populated room database to my ViewModel and currently getting:

"StandaloneCoroutine{Active}@933049a"

instead of the actual data.

I have tried using LiveData which only returned null which as far as I'm aware is because it was not observed.

Switched to coroutines which seemed to make more sense if my UI doesn't need the data anyway. I ended up with this so far:

DAO:

@Dao
interface WordListDao {
    @Query("SELECT word FROM wordlist WHERE used = 0 ORDER BY id DESC LIMIT 1")
    suspend fun readWord(): String

// tried multiple versions here only string can be converted from Job

//  @Query("SELECT * FROM wordlist WHERE used = 0 ORDER BY id DESC LIMIT 1")
//  fun readWord(): LiveData<WordList>

//  @Query("SELECT word FROM wordlist WHERE used = 0 ORDER BY id DESC LIMIT 1")
//  fun readWord(): WordList
}

repository:

class WordRepository(private val wordListDao: WordListDao) {

    //val readWordData: String = wordListDao.readWord()

    suspend fun readWord(): String {
       return wordListDao.readWord()
    }
}

model:

@Entity(tableName = "wordlist")
data class WordList(
    @PrimaryKey(autoGenerate = true)
    val id: Int,
    val word: String,
    var used: Boolean
)

VM:

class HomeViewModel(application: Application) : ViewModel() {

    private val repository: WordRepository
    private var word: String

    init {
        val wordDb = WordListDatabase.getDatabase(application)
        val wordDao = wordDb.wordlistDao()
        repository = WordRepository(wordDao)
        word = viewModelScope.launch {
                repository.readWord()
            }.toString()
        Log.d("TAG", ": $word") // does nothing?
    }
    println(word) // StandaloneCoroutine{Active}@933049a
}

This is the only way that I have managed to not get the result of:

Cannot access database on the main thread

There is a better way to do this, I just can't figure it out.

CodePudding user response:

The return value is as expected, because launch does always return a Job object representing the background process.

I do not know how you want to use the String for, but all operations which should be done after receiving the String must be moved inside the Coroutine or in a function which is called from the Coroutine.

viewModelScope.launch {
                val word = repository.readWord()
                
                // do stuff with word
                
                // switch to MainThread if needed
                launch(Dispatchers.Main){}
            }

CodePudding user response:

You can access the return value of repository.readWord() only inside the launch block.

viewModelScope.launch {
    val word = repository.readWord() 
    Log.d("TAG", ": $word")           // Here you will get the correct word
}

If you need to update you UI when this word is fetched from database, you need to use an observable data holder like a LiveData or StateFlow.

class HomeViewModel(application: Application) : ViewModel() {

    private val repository: WordRepository
    private val _wordFlow = MutableStateFlow("")  // A mutable version for use inside ViewModel
    val wordFlow = _word.asStateFlow()            // An immutable version for outsiders to read this state

    init {
        val wordDb = WordListDatabase.getDatabase(application)
        val wordDao = wordDb.wordlistDao()
        repository = WordRepository(wordDao)
        viewModelScope.launch {
            _wordFlow.value = repository.readWord()
        }
    }
}

You can collect this Flow in your UI layer,

someCoroutineScope {
    viewModel.wordFlow.collect { word ->
        // Update UI using this word
    }
}
  • Related