Home > Mobile >  Get Latest Data From Room with Paging 3 Android
Get Latest Data From Room with Paging 3 Android

Time:10-06

I want to show a list of items with pagination 3 and the data is from my local database with Room Library. I pre-populate my data to the Room Database with RoomDatabase.Callback provided by the room with JSON file. But the first time i open the app the list are not showing anything. I have to reopen my application and the items from the tables will shown.

Here are some snippets of the codes:

quiz.json

[
    {
        "question": "Question 1",
        "type": "en",
        "answer": 0
    },
    {
        "question": "Question 2",
        "type": "en",
        "answer": 1
    }
]

AppDatabase.kt

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

    abstract fun quizDao(): QuizDao

    private class AppDatabaseCallback(
        private val context: Context,
        private val scope: CoroutineScope
    ) : RoomDatabase.Callback() {

        override fun onCreate(db: SupportSQLiteDatabase) {
            super.onCreate(db)
            INSTANCE?.let { database ->
                scope.launch {
                    val quizDao = database.quizDao()
                    quizDao.deleteAll()

                    fillQuizData(context, quizDao)
                }
            }
        }

      suspend fun fillQuizData(context: Context, quizDao: QuizDao) {
            val jsonArray = FileUtil.loadJsonArray(context, R.raw.quiz)
            try {
                jsonArray?.let {
                    for (i in 0 until it.length()) {
                        val item = it.getJSONObject(i)
                        val isAnswer = item.getInt("answer")
                        quizDao.insert(
                            QuizEntity(
                                0,
                                item.getString("question"),
                                item.getString("type"),
                                isAnswer == 1,
                            )
                        )
                    }
                }
            } catch (exception: JSONException) {
                exception.printStackTrace()
            }
        }
    }

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

        fun getDatabase(
            context: Context,
            scope: CoroutineScope
        ): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "MyApplication.db"
                )
                    .addCallback(AppDatabaseCallback(context, scope))
                    .fallbackToDestructiveMigration()
                    .build()
                INSTANCE = instance
                instance
            }
        }
    }
}

QuizDao.kt

@Dao
interface QuizDao {

    @Query("SELECT * FROM quiz WHERE type=:type AND question LIKE '%' || :question || '%' LIMIT :size")
    suspend fun searchQuiz(type: String, size: Int, question: String = ""): List<QuizEntity>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(quizEntity: QuizEntity)

    @Query("DELETE FROM quiz")
    suspend fun deleteAll()
}

QuizRepository.kt

class QuizRepository @Inject constructor(private val quizDao: QuizDao) {

  fun getEnglishQuiz(query: String = ""): Flow < PagingData < QuizEntity >> {
    return Pager(PagingConfig(PAGE_SIZE)) {
      QuizPagingSource(quizDao, "en", query)
    }.flow
  }

  companion object {
    private
    const val PAGE_SIZE = 25
  }
}

QuizViewModel.kt

@HiltViewModel
class QuizViewModel @Inject constructor(private val quizRepository: QuizRepository) : ViewModel() {

    fun getEnglishQuizData(query: String = ""): Flow<PagingData<Quiz>> {
        return quizRepository.getEnglishQuiz(query).map {
            it.map { quiz ->
                Quiz(
                    quiz.id,
                    quiz.question,
                    quiz.type,
                    quiz.isAnswer
                )
            }
        }.flowOn(Dispatchers.IO).cachedIn(viewModelScope)
    }
}

In My Activity:

lifecycleScope.launch {
  binding.etSearch.getQueryTextChangeStateFlow()
    .debounce(500)
    .distinctUntilChanged()
    .flatMapLatest {
      query - >
        quizViewModel.getEnglishQuizData(query)
    }
    .collect {
      result - >
        quizAdapter.submitData(result)
    }
}

Extensions for SearchView

fun SearchView.getQueryTextChangeStateFlow(): StateFlow < String > {

  val query = MutableStateFlow("")

  setOnQueryTextListener(object: SearchView.OnQueryTextListener {
    override fun onQueryTextSubmit(query: String ? ): Boolean {
      return true
    }

    override fun onQueryTextChange(newText: String): Boolean {
      query.value = newText
      return true
    }
  })

  return query

}

I have thought about adding one new function for my QuizDao which return LiveData of my QuizEntity and i observed it in my activity and check if the size is not the same i will call functions provided by paging 3 library which is either adapter.refreshed() or adapter.invalidated() if i'm not wrong. I haven't tried that so i don't know if that is works. Any solution?? Sorry for my bad english.. Thanks.

CodePudding user response:

Paging is notified of updates to DB via invalidation, which generates a new PagingData in the Flow. In your activity, make sure to use .collectLatest instead of .collect, so that you cancel the previous (empty) generation and use the new one once you receive it downstream.

e.g.,

lifecycleScope.launch {
  binding.etSearch.getQueryTextChangeStateFlow()
    ...
    .collectLatest {
      result - >
        quizAdapter.submitData(result)
    }
}

.submitData suspends until that generation is finished. Since Paging is powered by collector's scope, you shouldn't depend on it to eagerly cancel.

  • Related