0

My database query operations can take a long time, so I want to display a ProgressBar while the query is in progress. This is especially a problem when the user changes the sorting options, because it displays the old list for a while until the new list comes in and the RecyclerView is updated. I just don't know where to capture the Loading and Success states for a query like this.

Here's my method for getting the PagedList from the database:

fun getGameList(): LiveData<PagedList<Game>> {

    // Builds a SimpleSQLiteQuery to be used with @RawQuery
    val query = buildGameListQuery()

    val dataSourceFactory: DataSource.Factory<Int, Game> = database.gameDao.getGameList(query)

    val data: LiveData<PagedList<Game>> = LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE)
        .build()

    return data
}

And I update my list by observing this:

val games = Transformations.switchMap(gameRepository.sortOptions) {
    gameRepository.getGameList()
}

Do I need a custom DataSource and DataSource.Factory? If so, I have no idea where to even begin with that. I believe it would be a PositionalDataSource, but I can't find any examples online for implementing a custom one.

I also tried adapter.registerAdapterDataObserver() on my RecyclerView adapter. This fires various callbacks when the new list data is being displayed, but I can't discern from the callbacks when loading has started and stopped.

Gavin Wright
  • 3,124
  • 3
  • 14
  • 35

1 Answers1

0

I was ultimately able to fix this by observing the games LiveData. However, it wasn't exactly straightforward.

Here's my DatabaseState class:

sealed class DatabaseState {

    object Success : DatabaseState()

    object LoadingSortChange: DatabaseState()

    object Loading: DatabaseState()
}

Capturing the Loading state was easy. Whenever the user updates the sort options, I call a method like this:

fun updateSortOptions(newSortOptions: SortOptions) {
    _databaseState.value = DatabaseState.LoadingSortChange
    _sortOptions.value = newSortOptions
}

The Success state was the tricky one. Since my sorting options are contained in a separate Fragment from the RecyclerView, the games LiveData observer fires twice upon saving new sort options (once when ListFragment resumes, and then again a bit later once the database query is completed). So I had to account for this like so:

/**
 * The observer that triggers this method fires once under normal circumstances, but fires
 * twice if the sort options change. When sort options change, the "success" state doesn't occur
 * until the second firing. So in this case, DatabaseState transitions from LoadingSortChange to
 * Loading, and finally to Success.
 */
fun updateDatabaseState() {
    when (databaseState.value) {
        Database.LoadingSortChange -> gameRepository.updateDatabaseState(DatabaseState.Loading)
        DatabaseState.Loading -> gameRepository.updateDatabaseState(DatabaseState.Success)
    }
}

Finally, I needed to make some changes to my BindingAdapter to smooth out some remaining issues:

@BindingAdapter("gameListData", "databaseState")
fun RecyclerView.bindListRecyclerView(gameList: PagedList<Game>?, databaseState: DatabaseState) {
    val adapter = adapter as GameGridAdapter

    /**
     * We need to null out the old list or else the old games will briefly appear on screen
     * after the ProgressBar disappears.
     */
    adapter.submitList(null)

    adapter.submitList(gameList) {
        // This Runnable moves the list back to the top when changing sort options
        if (databaseState == DatabaseState.Loading) {
            scrollToPosition(0)
        }
    }
}
Gavin Wright
  • 3,124
  • 3
  • 14
  • 35