Search code examples
androidandroid-roomkotlin-coroutinesandroid-livedata

Android Room Pre-populated Data not visible first time


Freshly installing the app, the view model doesn't bind the data. Closing the app and opening it again shows the data on the screen.

Is there any problem with the pre-population of data or is the use of coroutine is not correct?

If I use Flow in place of LiveData, it collects the data on the go and works completely fine, but its a bit slow as it is emitting data in the stream.

Also, for testing, The data didn't load either LiveData/Flow. Tried adding the EspressoIdlingResource and IdlingResourcesForDataBinding as given here

Room creation

  @Provides
  @Singleton
  fun provideAppDatabase(
    @ApplicationContext context: Context,
    callback: AppDatabaseCallback
  ): AppDatabase {
    return Room
      .databaseBuilder(context, AppDatabase::class.java, "database_name")
      .addCallback(callback)
      .build()

AppDatabaseCallback.kt

 override fun onCreate(db: SupportSQLiteDatabase) {
    super.onCreate(db)

    CoroutineScope(Dispatchers.IO).launch {
      val data = computePrepopulateData(assets_file_name)
      data.forEach { user ->
        dao.get().insert(user)
      }
    }
  }

Dao

@Insert(onConflict = OnConflictStrategy.REPLACE)
  suspend fun insertUser(user: User)

@Query("SELECT * FROM $table_name")
  suspend fun getAllUser(): List<User>

ViewModel

CoroutineScope(Dispatchers.IO).launch {
      repository.getData().let {
        listUser.postValue(it)
      }
    }

Attaching the data using BindingAdapter

app:list="@{viewModel.listUser}"

Solution

  • Your DAO returns suspend fun getAllUser(): List<User>, meaning it's a one time thing. So when the app starts the first time, the DB initialization is not complete, and you get an empty list because the DB is empty. Running the app the second time, the initialization is complete so you get the data.

    How to fix it:

    1. Switch getAllUser() to return a Flow:
    // annotations omitted
    fun getAllUser(): Flow<List<User>>
    
    1. Switch insertUser to use a List
    // annotations omitted
    suspend fun insertUser(users: List<User>)
    

    The reason for this change is reducing the number of times the Flow will emit. Every time the DB changes, the Flow will emit a new list. By inserting a List<User> instead of inserting a single User many times the (on the first run) Flow will emit twice (an empty list + the full list) compared to number of user times with a single insert.

    Another way to solve this issue is to use a transaction + insert a single user.

    1. I recommend you use viewModelScope inside the ViewModel to launch coroutines so it's properly canceled when the ViewModel is destroyed.