Reputation: 91
I'm trying to get the last id added from entity A to entity B to add to entity B by it , I fetched the id of the last element added to entity A like this :
in Dao :
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(addSpendEntity: AddSpendEntity) : Long
and in fun insert in repo i used mutableLiveData to save the last id inserted and get it to viewmodel then to observing it in fragment
in repo :
class AddSpendRepository(private var database: PersonalAccountingDateBase) {
private var id : Long = 0
private var mutableLiveData = MutableLiveData<Long>()
suspend fun insert(addSpendEntity: AddSpendEntity){
id = database.getAddSpendDao().insert(addSpendEntity)
Log.e("addspendrepository",id.toString())
mutableLiveData.postvalue(id)
Log.e("addspendrepositoryid",mutableLiveData.value.toString())
...
}}
fun getMutableLiveData() : MutableLiveData<Long> = mutableLiveData
and in VM :
fun insertSpend(addSpendEntity: AddSpendEntity) = viewModelScope.launch(Dispatchers.IO) {
addSpendRepository.insert(addSpendEntity)
}
fun getMutableLiveData() : MutableLiveData<Long> = addSpendRepository.getMutableLiveData()
the observer in fragment i try to add to entity B When mutableLiveData is change :
private fun insert()
{
val totalMoney = binding.edtAddSpendSpendMoney.text.toString().toInt()
val notice = binding.edtAddSpendNotice.text.toString()
val date = binding.txtAddSpendDateText.text.toString()
val addSpendEntity = AddSpendEntity(totalMoney,notice,date)
addSpendViewModel.insertSpend(addSpendEntity)
addSpendViewModel.getMutableLiveData().observe(viewLifecycleOwner,
Observer {
Log.e("addspendfragment",it.toString())
if(it.toInt() != 0)
{
val dailyMovementEntity = DailyMovementEntity("make",totalMoney,notice,5,it.toInt())
addSpendViewModel.insertDailyMovement(dailyMovementEntity)
}
})
so the problem i faced is when to insert in the first time the value of mutable get null and the observer does'nt notice any thing then in the second time the observer notice the previos state of id and this condition continues as long as the application is running , when i close the app and do the same in the same way : The same problem is repeated as shown
Upvotes: 0
Views: 809
Reputation: 93531
You didn't show it in your code, so I'm just guessing, but here's a possible cause of your issue.
I'm guessing your ViewModel's insertSpend
function is doing something like this:
fun insertSpend(addSpendEntity: AddSpendEntity) {
viewModelScope.launch(Dispatchers.IO) {
repository.insert(addSpendEntity)
}
}
The problem is, if you call MutableLiveData.value
on a thread other than the main thread, then the change is not viewable until another loop of the main thread has occurred. You're not supposed to call .value
on any thread besides the main thread. Then you get the proper value in your observer because observers are called on the next loop of the main thread.
Also, a suspend function should never require being called from a specific dispatcher, so you should not need to specify Dispatchers.IO
when you launch your coroutine. More properly, your repository function should look like this, so it is safe to call it from anywhere. Any time a suspend function calls a function that requires a specific dispatcher, it is best to specify that dispatcher internally (I think of this as an extension of the single responsibility principle--outside functions shouldn't have to know what state to specify when calling another function if it can be avoided).
I would define it like this:
suspend fun insert(addSpendEntity: AddSpendEntity) = withContext(Dispatchers.Main) {
id = database.getAddSpendDao().insert(addSpendEntity) // it's safe to call this on main because it's a suspend function which by convention must not block
Log.e("addspendrepository",id.toString())
mutableLiveData.value = id
Log.e("addspendrepositoryid",mutableLiveData.value.toString())
// ...
}
Just my opinion:
On the ViewModel side, in general, you should rarely ever be launching a coroutine on the ViewModel scope with a specific dispatcher. Android has a general convention of treating the main thread as the default, and it is full of functions that must be called on main for proper behavior. So it is clean to always leave that as your default and only use withContext(Dispatchers.IO)
(or .Default
) for the bits of your coroutine that need it. And you should never need those just to call suspend functions, because of the coroutine convention that suspend functions must never block. So you only need them when calling blocking code.
Upvotes: 1