Android LiveData fires with stale emission . . . only sometimes . . . after Room DB update

Viewed 128

I am using LiveData to observe a Room query that filters a table on a boolean in the table. The boolean is whether the row has been uploaded to an external database or not.

Here is the query I'm observing:

memodao.kt

@Query("SELECT * FROM memos WHERE uploaded = 0 ORDER BY id ASC")
fun getUnsyncedMemos(): LiveData<List<Memo>>


//including this statement because it's used later
@Update
suspend fun update(memo: Memo): Int

When the LiveData fires, I grab that row, upload it to the external database using Retrofit, and then when Retrofit returns I mark that row as uploaded to exclude it from the above query.

Writing to the table I am observing causes the above query to trigger again, as expected.

The trouble is, from time to time the query returns the same row again, even though it NO LONGER meets the where clause.

It actually returns a stale version of the row, that still meets the where clause.

It only happens from time to time. To me it feels like a race condition: the beginning of the Room @Update statement triggers the @Query to execute again, and sometimes the row hasn't been rewritten in time, so the @Query returns a stale result.

Here is the rest of the relevant code.

CloudSyncService.kt

mMemoRepository.getUnsyncedMemos().observeForever{ memoList ->
   memoList.forEach { memo ->
        if (!memo.uploaded) uploadMemo(memo) //an unnecessary if, I know.  
   }
}

private fun uploadMemo(memo: Memo) {
    mMemoRepository.uploadMemo(memo).observeOnce(this, Observer {
        mCloudOK = it != CLOUD_FAILED
    })
}



MemoRepository.kt

override fun getUnsyncedMemos() = memoDao.getUnsyncedMemos()

override fun uploadMemo(memo: Memo): LiveData<Int> {
    val mutableLiveData = MutableLiveData<Int>()
    CoroutineScope(IO).launch{
        memoWebService.uploadMemo(memo).enqueue(object : Callback<Memo> {
            override fun onResponse(call: Call<Memo>, response: Response<Memo>) {
                if (response.isSuccessful) {
                    memo.uploaded = true
                    updateMemo(memo)
                    mutableLiveData.value = CLOUD_OK
                }
                else {
                    mutableLiveData.value = CLOUD_UNKNOWN
                }
            }
            override fun onFailure(call: Call<Memo>, t: Throwable) {
                mutableLiveData.value = CLOUD_FAILED
                mMutableErrorMessage.value = t.message
            }
        })
    }
    return mutableLiveData
}

I'm wondering if it's an issue with the co-routines throwing things out of sync and creating a race? I've moved calls in and out of co-routines to try to fix it, but I still get the problem.

I have read about the issue where LiveData returns stale data: Room LiveData fires twice with one stale emission

I believe my question differs, because I am not adding new observers that receive stale data in the first firing. I have one observer, observing forever, that is occasionally getting stale data after a db @Update.

Many thanks!

0 Answers
Related