There are two ways i have tried to iterate over large Room database:
1- Get Cursor
and iterate over that:
import android.database.Cursor
...
@Query("SELECT * FROM Category")
fun getAllCategory(): Cursor<Category>
...
val success = cursor.moveToFirst()
if (success) {
while (!cursor.isAfterLast) {
// Process items
cursor.moveToNext()
}
} else {
// Empty
}
cursor.close()
2- Use PagedList
to get pageSize amount of items at once and process. Then query another page and process:
@Query("SELECT * FROM Category")
fun getAllCategory(): DataSource.Factory<Int, Category>
// Here i will return Flowable. You can return LiveData with 'LivePagedListBuilder'
fun getCategories(pageSize: Int): Flowable<PagedList<Category>> {
val config = PagedList.Config.Builder()
.setPageSize(pageSize)
.setPrefetchDistance(pageSize / 4)
.setEnablePlaceholders(true)
.setInitialLoadSizeHint(pageSize)
.build()
return RxPagedListBuilder(categoryDao.getAllCategory(), config)
.buildFlowable(BackpressureStrategy.BUFFER)
}
Now above getCategories()
function will return pagedList
inside Flowable
or LiveData
. Since we have set setEnablePlaceholders(true)
, pagedList.size
will show the whole size even if it is not in memory. So, if pageSize
is 50 and all data size is 1000, pagedList.size
will return 1000, but most of them will be null. To query next page and process:
// Callback is triggered when next page is loaded
pagedList.addWeakCallback(pagedList.snapshot(), object : PagedList.Callback() {
override fun onChanged(position: Int, count: Int) {
for (index in position until (position + count)) {
if (index == (position + count - 1)) {
if (index < (pagedList.size - 1))
pagedList.loadAround(index + 1)
else{
// Last item is processed.
}
} else
processCurrentValue(index, pagedList[index]!!)
}
}
override fun onInserted(position: Int, count: Int) {
// You better not change database while iterating over it
}
override fun onRemoved(position: Int, count: Int) {
// You better not change database while iterating over it
}
})
// Start to iterate and query next page when item is null.
for (index in 0 until pagedList.size) {
if (pagedList[index] != null) {
processCurrentValue(index, pagedList[index]!!)
} else {
// Query next page
pagedList.loadAround(index)
break
}
}
Conclusion: In PagedList
approach, you can get thousands of rows at once and process, while in Cursor
approach you iterate row by row. I found PagedList
unstable when pageSize > 3000. It does not return the page sometimes. So i used Cursor
. It takes roughly about 5 minutes to iterate over (and process) 900k rows on both approaches on Android 8 phone.