使用 Paging 3 从 Room 获取最新数据 Android
Get Latest Data From Room with Paging 3 Android
我想显示分页为 3 的项目列表,数据来自我的本地数据库和 Room Library。我使用 JSON 文件的房间提供的 RoomDatabase.Callback 将我的数据预填充到房间数据库中。但是我第一次打开应用程序时,列表没有显示任何内容。我必须重新打开我的应用程序,表格中的项目将会显示。
以下是一些代码片段:
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)
}
}
在我的 Activity:
lifecycleScope.launch {
binding.etSearch.getQueryTextChangeStateFlow()
.debounce(500)
.distinctUntilChanged()
.flatMapLatest {
query - >
quizViewModel.getEnglishQuizData(query)
}
.collect {
result - >
quizAdapter.submitData(result)
}
}
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
}
我考虑过为我的 QuizDao 添加一个新函数,它是我 QuizEntity 的 return LiveData 并且我在我的 activity 中观察到它并检查大小是否不一样我将调用函数由 paging 3 库提供,如果我没记错的话,它是 adapter.refreshed() 或 adapter.invalidated()。我没有尝试过,所以我不知道这是否有效。有什么解决办法吗??抱歉我的英语不好..谢谢.
分页通过失效通知 DB 更新,这会在 Flow
中生成一个新的 PagingData
。在你的 activity 中,确保使用 .collectLatest
而不是 .collect
,这样你就可以取消上一个(空的)生成并在下游接收到新的后使用它。
例如,
lifecycleScope.launch {
binding.etSearch.getQueryTextChangeStateFlow()
...
.collectLatest {
result - >
quizAdapter.submitData(result)
}
}
.submitData
暂停直到该代完成。由于 Paging 由收集器的范围提供支持,因此您不应依赖它来急切取消。
我想显示分页为 3 的项目列表,数据来自我的本地数据库和 Room Library。我使用 JSON 文件的房间提供的 RoomDatabase.Callback 将我的数据预填充到房间数据库中。但是我第一次打开应用程序时,列表没有显示任何内容。我必须重新打开我的应用程序,表格中的项目将会显示。
以下是一些代码片段:
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)
}
}
在我的 Activity:
lifecycleScope.launch {
binding.etSearch.getQueryTextChangeStateFlow()
.debounce(500)
.distinctUntilChanged()
.flatMapLatest {
query - >
quizViewModel.getEnglishQuizData(query)
}
.collect {
result - >
quizAdapter.submitData(result)
}
}
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
}
我考虑过为我的 QuizDao 添加一个新函数,它是我 QuizEntity 的 return LiveData 并且我在我的 activity 中观察到它并检查大小是否不一样我将调用函数由 paging 3 库提供,如果我没记错的话,它是 adapter.refreshed() 或 adapter.invalidated()。我没有尝试过,所以我不知道这是否有效。有什么解决办法吗??抱歉我的英语不好..谢谢.
分页通过失效通知 DB 更新,这会在 Flow
中生成一个新的 PagingData
。在你的 activity 中,确保使用 .collectLatest
而不是 .collect
,这样你就可以取消上一个(空的)生成并在下游接收到新的后使用它。
例如,
lifecycleScope.launch {
binding.etSearch.getQueryTextChangeStateFlow()
...
.collectLatest {
result - >
quizAdapter.submitData(result)
}
}
.submitData
暂停直到该代完成。由于 Paging 由收集器的范围提供支持,因此您不应依赖它来急切取消。