I am trying to migrate from LiveData to Kotlin Flow. Right now I am working on a project that has offline supports in Room.
I was looking through the documentation and I managed to write an observable query in coroutines with Flow. (see: here)
The problem that I am facing right now is that whenever I add the suspend keyword inside the DAO class and try to run the project, it fails with the following error:
error: Not sure how to convert a Cursor to this method's return type (kotlinx.coroutines.flow.Flow<MyModel>).
The code with the problem:
#Transaction
#Query("SELECT * FROM table WHERE status = :status LIMIT 1")
suspend fun getWithSpecificStatus(status: String): Flow<MyModel?>
I am calling the code like this:
val modelLiveData: LiveData<MyModel?> = liveData(Dispatchers.IO) {
val result = databaseService.getWithSpecificStatus(Enum.IN_PROGRESS.status).first()
result?.let {
emit(it)
}
}
I tried to keep things simple. why is my code failing?
You could directly initialise the value of modelLiveData as:
val modelLiveData=databaseService.
getWithSpecificStatus(Enum.IN_PROGRESS.status).first()
.asLiveData()
You have used Flow so asLiveData() is used to convert it to LiveData
Also suggestion , you do should not use the suspend keyword because when you are returning Flow, Room automatically does this asynchronously. You just need to consume the Flow.
For now I use this code in my DAO:
#Dao
interface NotesDao {
#Query("SELECT * FROM Notes")
fun getAllNotes(): List<Notes?>?
#Query("SELECT * FROM Notes WHERE not hidden AND not grouped")
fun getNotes(): List<Notes?>?
#Query("SELECT * FROM Notes WHERE id = :id")
fun getById(id: Long): Notes?
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(notes: Notes?)
#Update
fun update(notes: Notes?)
#Delete
fun delete(notes: Notes?)
}
But in many tutorials I either see Flowable<List<Notes>> instead of List<Notes> or LiveData<List<Notes>>.
Which approach is preferable?
There are two factors you should consider:
All interactions with database (both queries and updates) should be outside main thread.
In some use-cases you want single "one-shot" query, in other - you want your query to be "live" - i.e. to return updated result whenever data was changed (Observer pattern).
Using Kotlin you have two main options (what's better is a matter of taste. In some tutorials you can see also AsyncTask or Thread executors for switching off the main thread):
Use RxJava (use Flowable for "live" queries, Single - for "one-shot" queries and one of the Single/Completable/Maybe for insert/update/delete). Using RxJava you can use built-in mechanism for switching off the main thread.
Use Coroutines (and keyword suspend) for insert/update/delete, and LiveData or Flow (from coroutines library) for "live" queries, or suspend - for "one-shot" queries. suspend lets to switch from the main thread. If you use LiveData/Flow you don't need suspend, since Room does it itself.
See also official documentation for additional hints.
How can we get the callback for the 'insert' operation in repository as Livedata and I can pass back to viewmodel using coroutines?
Dao
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(user : User): Long
I am not sure you need callback if you use coroutines in your code.
If you make your insert function as suspend, you can call it from your ViewModel from inside coroutine (for example with viewModelScope). And inside this coroutine you'll move to the next line of code only after insert method would be done:
viewModelScope.launch{
val userId = repository.insert(someUser)
// <- there coroutine is suspended until insert is done. In a sense it's like a callback
someOtherMethod()
}
I made a simple example app with using Room and Flows:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val build = Room.databaseBuilder(this, FinanceDatabase::class.java, "database.db")
.fallbackToDestructiveMigration()
.build()
GlobalScope.launch {
build.currencyDao().addCurrency(CurrencyLocalEntity(1))
val toList = build.currencyDao().getAllCurrencies().toList()
Log.d("test", "list - $toList")
}
}
}
#Entity(tableName = "currency")
data class CurrencyLocalEntity(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "currencyId")
var id: Int
) {
constructor() : this(-1)
}
#Dao
interface CurrencyDao {
#Query("SELECT * FROM currency")
fun getAllCurrencies(): Flow<CurrencyLocalEntity>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun addCurrency(currency: CurrencyLocalEntity)
}
#Database(entities = [CurrencyLocalEntity::class], version = 1)
abstract class FinanceDatabase : RoomDatabase() {
abstract fun currencyDao(): CurrencyDao
}
I want to use toList() function as in code above but something gets wrong and even Log doesn't print. At the same time using collect() works fine and gives me all records.
Can anybody explain to me what is wrong? Thanks.
There are a couple things wrong here but I'll address the main issue.
Flows returned by room emit the result of the query everytime the database is modified. (This might be scoped to table changes instead of the whole database).
Since the database can change at any point in the future, the Flow will (more or less) never complete because a change can always happen.
Your calling toList() on the returned Flow will suspend forever, since the Flow never completes. This conceptually makes sense since Room cannot give you the list of every change that will happen, without waiting for it to happen.
With this, I'm sure you know why collect gives you the records and toList() doesn't.
What you probably want here is this.
#Query("SELECT * FROM currency")
fun getAllCurrencies(): Flow<List<CurrencyLocalEntity>>
With this you can get the first result of the query with Flow<...>.first().
Flow in Room is for observing Changes in table.
Whenever any changes are made to the table, independent of which row is changed, the query will be re-triggered and the Flow will emit again.
However, this behavior of the database also means that if we update an unrelated row, our Flow will emit again, with the same result. Because SQLite database triggers only allow notifications at table level and not at row level, Room can’t know what exactly has changed in the table data
Make sure that the same doa object you are using for retrieving the list, is used for updating the database.
other than that converting flow to livedata is done using asLivedata extension function
For me below solution works for updating the view with database table changes.
Solution: Same Dao Object should be used when we insert details into the room database and get information from DB.
If you are using a dagger hilt then
#Singleton annotation will work.
I hope this will solve your problem.
**getAllCurrencies()** function should be suspend.
Please check the syntax to collect List from Flow:
suspend fun <T> Flow<T>.toList(
destination: MutableList<T> = ArrayList()
): List<T> (source)
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/to-list.html
I am following kotlin android fundamental codelabs. There is a part where the app need a reference of all data from database
The following code is placed on ViewModel, database is passed as the instance of dao
val nights = database.getAllNights()
The implementation of getAllNights is as follows (In DAO)
#Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC")
fun getAllNights(): LiveData<List<SleepNight>>
All other database calls are delegated to private suspend function and dispatched to IO thread except that one.
Isn't this call block the main thread
Why is it allowed
CODELAB