Search code examples
androidtransactionsandroid-sqliteandroid-room

Transaction in Room Database returning LiveData


I'd like to prune a table from old records and then count the records in it.

@Transaction
private fun getOrdersCount(): LiveData<Int>
{
    val now = LocalDateTime.now()
    val firstOfMonth = now.withDayOfMonth(1)
    subop_pruning(firstOfMonth) // query that deletes older orders
    return subop_select_count() // query that returns the number of orders
}

unfortunately this query returns an error

error: Method annotated with @Transaction must not return deferred/async return type androidx.lifecycle.LiveData. Since transactions are thread confined and Room cannot guarantee that all queries in the method implementation are performed on the same thread, only synchronous @Transaction implemented methods are allowed. If a transaction is started and a change of thread is done and waited upon then a database deadlock can occur if the additional thread attempts to perform a query. This restrictions prevents such situation from occurring.

Now, i'd like to do the sequence of operations -delete- and -select count- sequentially and in one transaction and return the LiveData from the -select count- operation. This seems to me legitimate. Questions:

  1. Aren't the queries inside a transaction executed sequentially?
  2. Why the error?
  3. How to obtain the same behaviour, for example using a MutableLiveData and InvalidationTracker.Observer#onInvalidated notification if it's not possible to get LiveData from transactions?

Solution

  • Point 1 and 2 remain unanswered, anyway here how i bypassed the problem (as per point 3). Hope it helps someone:

    fun getOrderCount(): LiveData<Int> = object : LiveData<Int>(0) {
        val observer = object : InvalidationTracker.Observer("order_table") {
            override fun onInvalidated(tables: MutableSet<String>) {
                requestOrderCount()
            }
        }
    
        override fun onActive() {
            super.onActive()
            val tracker =
                MyRoomDatabase.getInstance(applicationContext).invalidationTracker;
            tracker.addObserver(observer)
            requestOrderCount()
        }
    
        override fun onInactive() {
            super.onInactive()
            val tracker =
                MyRoomDatabase.getInstance(applicationContext).invalidationTracker;
            tracker.removeObserver(observer)
        }
    
        private fun requestOrderCount() {
            // better to use coroutines than executors
            Executors.newSingleThreadExecutor().execute {
                postValue(
                    MyRoomDatabase.getInstance(applicationContext).OrderDao()
                        .getOrderCount_()
                )
            }
        }
    }
    
    
    @Transaction
    private fun getOrdersCount_(): LiveData<Int>
    {
        val now = LocalDateTime.now()
        val firstOfMonth = now.withDayOfMonth(1)
        subop_pruning(firstOfMonth) // query that deletes older orders
        return subop_select_count() // query that returns the number of orders
    }