I have a code
#Insert
fun insertAll(vararg todos: TodoItem)
And when I perform insertion, I like it to scroll to the bottom of the list after insertion.
fun addRecord(todoItem: TodoItem) {
viewModelScope.launch(Dispatchers.IO) {
todoDao.insertAll(todoItem)
scrollToBottom()
}
}
However I notice that insertAll is done asynchronously, and hence scrollToBottom() might be called before the insertion is completed.
I can use the below to notify on every changes happen, but that is not ideal, given the notification happens on all cases (UPDATE, DELETE, INSERT). I only want to scroll to the bottom on Insertion.
#Query("SELECT * FROM TodoItem")
fun getAll(): Flow<List<TodoItem>>
How can I detect insertion is completed?
Can I make insertAll synchornous?
Or can I detect what operation is performed when getting the notification getAll()?
Related
i am inserting and deleting a row in room database using following methods of ViewModel class
fun insert(rules: TableRules) = viewModelScope.launch {
repository.insert(rules)
}
fun delete(id: Int) = viewModelScope.launch {
repository.delete(id)
}
and retriving the data using this this method of DAO class
#Query("SELECT * FROM rules_table")
fun getAlphabetizedRules(): List<TableRules>
but i am not getting update data.
i.e when i add one row and then retrive, i will not get newly added row.
i close my app, remove from recent app list, start app again then retrive, then i will get that row.
same thing happens in case of delete also.
what i am missing i above.
Launching a coroutine queues up work that will complete in the future. If you launch a coroutine and then immediately check the state of the table without waiting for the coroutine to finish, you have a race condition and will likely get the earlier state returned.
You need to call getAlphabetizedRules() from inside the same coroutine that you launch to call insert() or delete() so it is happening after the database change.
Or alternatively, you can create a new coroutine or suspend function that joins the returned Job from your existing insert() and delete() functions. For example:
suspend fun deleteAndGetUpdatedList(id: Int): List<TableRules> {
delete(id).join()
return repository.getAlphabetizedRules()
}
By the way, in your DAO, getAlphabetizedRules() should be marked as a suspend function to make it easier to use properly (not having to use withContext(Dispatchers.IO) { } every time you call it.
Mark the DAO method with #RawQuery annotation instead of normal #Query.
I have this basic Android Architecture Component use-case where I observe a live-data and update UI.
myLiveData.observe(viewLifecycleOwner, Observer {
// update UI
})
// will trigger a network call and update the repository (Room DB)
myViewModel.refreshDataFromRepository()
With the observe call, I get the trigger and I update the UI, which is fine. However, I also need to update the DB if the backend data has changed, so I trigger a network refresh and that updates the DB and triggers the observer once again. So, I am getting data twice.
I can change conflict strategy to ignore to avoid but I need to set it to "replace" as the data may change:
#Query("SELECT * FROM my_table ORDER BY timestamp DESC")
fun getMyEntities(): LiveData<List<MyEntity>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun add(myEntity: MyEntity)
Other option is to compare the network response with DB contents and avoid re-writing identical data but would be expensive.
Is there any way, database access can be made intelligent to avoid trigger if data has not changed?
You could use distinctUntilChanged, it has been added to Transformations:
myLiveData.distinctUntilChanged().observe(viewLifecycleOwner) {
// update UI
}
I get this error: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
It happens when I launch fun turnAllWordsOn() in the ViewModel (code below). This function launches coroutine, and I thought that coroutine always works on the backgroung thread. So why I get this error?
Apprecieate any help
In Fragment:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_turn_all_words_on -> {
viewModel.turnAllWordsOn()
true
}
// othes items
}
In ViewModel:
fun turnAllWordsOn() = viewModelScope.launch {
wordDao.turnAllWordsOn()
}
In Dao:
#Query("UPDATE word_table SET shown = 1")
fun turnAllWordsOn()
You have to mark your Dao function as a suspend function if you want Room to run it on a background thread. Otherwise all you're doing is calling a synchronous function from a coroutine scope.
#Query("UPDATE word_table SET shown = 1")
suspend fun turnAllWordsOn()
As a side note, suspend functions don't automatically run on a background thread, however Room does the necessary work behind the scenes when you mark a query as suspend.
Even if you have the answer, I still want to give some solution and cause of the problem,
Performing networking, or accessing the database can block Ui Thread, so you can use
Using RxJava:
Completable.fromAction {
wordDao.turnAllWordsOn()
}
Using Coroutine:
#Query("UPDATE word_table SET shown = 1")
suspend fun turnAllWordsOn()
Just a note for anyone who ends up here the accepted answer is correct, however if you are calling a Dao function from an activity you must put in inside a Coroutine scope. So after you add suspend onto your Dao function call it from the activity like this.
lifecycleScope.launch(Dispatchers.IO){
// call Dao function here
}
sopuse I have a function in my Room DAO like this:
#Query("SELECT * FROM cached_tbl ORDER BY id")
fun getAll(): Flowable<List<Item>>
this returns all the items in the database
but I don't want this, I want the data to be paginated and be emitted in little chunks. I want the data to be loaded from the database on demand, for example, 100 items per page. is there a way to do that?
Jetpack has a library for this called Paging which you might be interested in. The good thing about using Room is that it also has Paging integration, so setup will go something like:
Dao
#Query("SELECT * FROM cached_tbl ORDER BY id")
fun getAll(): PagingSource<Int, Item>
ViewModel
val pager = Pager(PagingConfig(pageSize)) { dao.getAll() }
.cachedIn(viewModelScope)
Activity
lifecycleScope.launch {
pager.flow.collectLatest {
PagingDataAdapter.submtiData(it)
}
}
You can read quite a bit more here: https://developer.android.com/topic/libraries/architecture/paging/v3-overview, including how to setup a layered source, transformations and more.
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