5

I am writing a toy Android app using Kotlin flow and Android Paging 3 library. The app calls some remote API to get a list of photos, and display them using a RecyclerView with a PagingDataAdapter.

I find that the code after pagingAdapter.submitData() is not executed.

Here is the code snippet (this function is in a Fragment):

  fun refreshList() {
    lifecycleScope.launch {
      photosViewModel.listPhotos().collect {
        // `it` is PagingData<Photo>
        pagingAdapter.submitData(it)
        Log.e(TAG, "After submitData")
      }
    }
  }

The log After submitData is not printed.

However, if I put the logging in front of the pagingAdapter.submitData() line, it is printed, like this:

  fun refreshList() {
    lifecycleScope.launch {
      photosViewModel.listPhotos().collect {
        // `it` is PagingData<Photo>
        Log.e(TAG, "Before submitData")
        pagingAdapter.submitData(it)
      }
    }
  }

The log Before submitData is printed with no problem.

Why does this happen, please?

seemuch
  • 233
  • 1
  • 2
  • 11
  • 1
    If the call to collect is on a background thread i would assume that submitData(it) is taking a long time to complete, put a before and after log, add breakpoints with both and then check if you hit both. Another idea is add breakpoint on submitData and step into the method and see what is taking so long – Brandon Jan 06 '21 at 06:56
  • What is the reason having `pagingAdapter.submitData(it)` in a coroutine scope? This is UI realated, so you should consider not running it in a coroutine. The data you are fetching can be fetched off the UI thread using coroutines, but the result, should be emitted (in your viewModel) on the `Dispatchers.Main`. You should consider mapping your `Flow` to `LiveData` – ChristianB Jan 06 '21 at 09:54
  • @ChristianB OP's code runs entirely on the UI thread. `lifecycleScope.coroutineContext[ContinuationInterceptor] == Dispatchers.Main` – Marko Topolnik Jan 07 '21 at 13:18
  • @ChristianB because `submitData` is a suspend function. It has to be inside a coroutine scope. – seemuch Jan 29 '21 at 17:04
  • @seemuch I didn't know `submitData` is a suspend function. Then it makes sense. Thanks – ChristianB Jan 29 '21 at 17:15

1 Answers1

3

.submitData is a suspending function which does not return until invalidation or refresh. As long as Paging is actively loading (collecting) from the PagingData you provided, it will not finish. This is why it must be done in a launched job.

For the same reason, make sure to use collectLatest instead of collect to make sure you cancel and start displaying new generations as soon as possible.

dlam
  • 3,547
  • 17
  • 20
  • Thank you for replying! I tried replacing `collect` with `collectLatest`, but the problem is still there. I think I do not understand the concept of suspending function in Kotlin well enough. Did I understand it correctly that in this case, the `submitData` function never returns? – seemuch Jan 29 '21 at 17:03
  • Yes `.submitData` suspends until Paging invalidates that `PagingData` or all of paging stops. This is why it must be launched in a separate job in some scope and that you must use `collectLatest`, because invalidation just emits a new `PagingData`, but you still need to cancel the old one in order to receive the new value. – dlam Jan 29 '21 at 19:40