Search code examples
androidkotlinandroid-jetpack-composeandroid-room

How to update a Room Flow using multiple database instances?


I'm trying to live count some value, when I insert stuff in db and get it with getCount() it is returning it well, but I want to live update Text in Bar. It updates after app restart, but not updating live, am I missing something?

Viewmodel:

@HiltViewModel
class appViewModel @Inject constructor(
    private val appDao: appDao
) : ViewModel() {

    private val _Count = MutableStateFlow(0)
    val Count: StateFlow<Int> = _Count.asStateFlow()

    init {
        viewModelScope.launch {
            appDao.getCount()
                .collect { count ->
                    _Count.value = count
                }
        }
    }
}

Bar:

@Composable
fun WkTopBar(navController: NavController?) {

    val appViewModel: appViewModel = hiltViewModel()
    val Count by appViewModel.Count.collectAsState()

    Text(text = "count: $Count", color = Color.Black)
}

Dao:


@Entity(tableName = "books")
data class Book(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val title: String,
    val body: String,
    val isRead: Boolean = false

)

@Dao
interface appDao {

    @Insert
    suspend fun insertBook(book: Book)

    @Query("SELECT COUNT(*) FROM books WHERE isRead = 0")
    fun getCount(): Flow<Int>
}

Database module


@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {

    @Provides
    fun provideAppDatabase(applicationContext: Context): AppDatabase {
        return Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java,
            "book_database"
        ).build()
    }

    @Provides
    fun provideAppDao(appDatabase: AppDatabase): appDao {
        return appDatabase.appDao()
    }

    @Provides
    fun provideContext(app: Application): Context {
        return app.applicationContext
    }
}

AppDatabase

@Database(entities = [Book::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
    abstract fun appDao(): appDao

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "book_database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

Inserting (this is done from another class running in another process where I do not use Hilt):

private fun saveBook(message: RemoteMessage) {
        val title = message.book?.title.orEmpty()
        val body = message.book?.body.orEmpty()

        val book = Book(title = title, body = body)

        CoroutineScope(Dispatchers.IO).launch {
            try {
                val appDao = AppDatabase.getDatabase(applicationContext).appDao()
                appDao.insertBook(book)
            } catch (e: Exception) {
                Log.e(TAG, "Error saving book: ${e.message}")
            }
        }
    }

I tried with Flow and LiveData, but it's not working.


Solution

  • The database is only notified about changes if the changes are done with the same database instance. Since you use two different database instances - one for reading (injected by Hilt) and one for writing (manually created) - the changes done by the latter won't automatically trigger an update for the first.

    If the code for both runs in the same process then the solution is simple: Use the same instance. The easiest way would be to also use Hilt to inject the database for saveBook. If you don't want to use Hilt for that, make sure you provide the same database instance for Hilt that you use to directly retrieve the dao.

    If the code runs in different processes then you need to enable multi-instance invalidation. That allows the database instance that makes changes to broadcast to other database instances that some of their current state is invalid. The other database instances then re-execute their queries to get the updated data. Simply add enableMultiInstanceInvalidation() to both your database builders:

    Room.databaseBuilder(/*...*/)
        .enableMultiInstanceInvalidation()
        .build()