Wednesday, 2 September 2020

How Do We Handle Runtime Errors From Room-Generated Paging Code?

In this sample project, I have a DAO that is using Paging 2 on an FTS4 table to search it, using a user-supplied search expression:

  @Query("SELECT snippet(paragraphsFts) FROM paragraphs JOIN paragraphsFts "+
      "ON paragraphs.id == paragraphsFts.rowid WHERE paragraphsFts.prose "+
      "MATCH :search ORDER BY sequence")
  abstract fun filtered(search: String): DataSource.Factory<Int, String>

I have a BookRepository with a filtered() function that just passes through the call to the DAO, and I have a SearchViewModel that converts the DataSource.Factory to a LiveData:

class SearchViewModel(search: String, repo: BookRepository) : ViewModel() {
  val paragraphs = repo.filtered(search).toLiveData(pageSize = 15)
}

And I have a fragment that observes the results:

    vm.paragraphs.observe(this.viewLifecycleOwner) {
      adapter.submitList(it)
    }

This works fine, so long as the user does not enter a search expression with a syntax error. In that case, Room throws an exception, and I cannot see where I can catch it.

2020-08-30 08:50:50.923 13948-14019 E/AndroidRuntime: FATAL EXCEPTION: arch_disk_io_3
    Process: com.commonsware.room.pagedfts, PID: 13948
    android.database.sqlite.SQLiteException: malformed MATCH expression: [-9] (code 1 SQLITE_ERROR)
        at android.database.sqlite.SQLiteConnection.nativeExecuteForCursorWindow(Native Method)
        at android.database.sqlite.SQLiteConnection.executeForCursorWindow(SQLiteConnection.java:942)
        at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:838)
        at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62)
        at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:153)
        at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:140)
        at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:232)
        at android.database.AbstractCursor.moveToFirst(AbstractCursor.java:271)
        at androidx.room.paging.LimitOffsetDataSource.countItems(LimitOffsetDataSource.java:89)
        at androidx.room.paging.LimitOffsetDataSource.loadInitial(LimitOffsetDataSource.java:119)
        at androidx.paging.PositionalDataSource.dispatchLoadInitial(PositionalDataSource.java:286)
        at androidx.paging.TiledPagedList.<init>(TiledPagedList.java:107)
        at androidx.paging.PagedList.create(PagedList.java:229)
        at androidx.paging.PagedList$Builder.build(PagedList.java:388)
        at androidx.paging.LivePagedListBuilder$1.compute(LivePagedListBuilder.java:206)
        at androidx.paging.LivePagedListBuilder$1.compute(LivePagedListBuilder.java:171)
        at androidx.lifecycle.ComputableLiveData$2.run(ComputableLiveData.java:101)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:919)

The exception is occurring on a background thread used by the LiveData created by toLiveData(). So a try/catch in my paragraphs declaration will not work, as the exception is not thrown during creation of the LiveData. Wrapping my observe() call in a try/catch does not work, as the exception is not thrown on that thread.

If I were not using Paging, and instead had my DAO's filtered() function be a suspend function, I would get the exception directly and can catch and handle it in the viewmodel. Or, if filtered() were a blocking call, I would get the exception directly. Or, if filtered() returned an RxJava type, I would get the exception through normal RxJava exception handling approaches (e.g., onError lambdas). But I cannot quite figure out where I should be intercepting this exception when using Paging.

It's possible that Paging 3 has a solution for this, which would be great to know, though Paging 3 right now is still in alpha.

While I have not tried it, I suspect that if I supply my own Executor to toLiveData() that I could catch the exception there. Even if that is a possible workound, that seems like I am relying on a side effect (replacing how we schedule work on threads to be able to catch an exception).



from How Do We Handle Runtime Errors From Room-Generated Paging Code?

No comments:

Post a Comment