androiddatasourceandroid-architecture-componentsandroid-livedatapagedlist

Observe not called with LiveData<PagedList>


I'm using the PagedList architectural components using a Room database and I am having trouble returning results to the observe method.

Here is my Dao:

@Dao
interface WorkPackageStorageDao {

    @Query("SELECT * from workpackages where id = :id")
    fun getById(id: String): Workpackage

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(workpackage: Workpackage)

    @Query("Select * from workpackages WHERE id LIKE '%' || :searchString || '%' order by :orderBy")
    fun searchWorkpackages(searchString : String, orderBy : String) : DataSource.Factory<Int, Workpackage>

    @Query("SELECT * FROM workpackages")
    fun searchWorkPackgesTest() : List<Workpackage>

    @Query("Select * from workpackages WHERE id LIKE '%' || :searchString || '%' order by :orderBy")
    fun searchWorkPackgesTestQuery(searchString : String, orderBy : String) : List<Workpackage>

    @Query("DELETE from workpackages")
    fun deleteAll()

}

My repository:

fun getAllWorkPackagesTestQuery() : List<Workpackage> {
    return workpackagesDao.searchWorkPackgesTestQuery("",SortedBy.WorkPackageNumber.type)
}

fun getAllWorkPackages() : DataSource.Factory<Int, Workpackage> {
    return getSortedAndSearchedWorkPackages("",
        SortedBy.WorkPackageNumber
    )
}

fun getSortedAndSearchedWorkPackages(searchString : String, sortBy: SortedBy) : DataSource.Factory<Int, Workpackage> {
    return workpackagesDao.searchWorkpackages(searchString,sortBy.type)
}

Here is the method in my view model:

suspend fun fetchWorkPackagesInitial(
    workWeek: Configurations.AppWeek,
    pagingLimit: Int
) {

    coroutineScope {
        withContext(Dispatchers.IO) {
            val factory: DataSource.Factory<Int, Workpackage> =
                workPackageRepository.getAllWorkPackages()

            val pagedListBuilder =
                LivePagedListBuilder<Int, Workpackage>(factory, pagingLimit)
            workPackagesList = pagedListBuilder.build() 
            val list = workPackageRepository.getAllWorkPackagesTestQuery() //27 Items returned, query is fine. 
        }

    }
}

Here is my fragment:

    mainViewModel.week.observe(this, Observer {
        it ?: return@Observer

        launch { workPackagesViewModel.fetchWorkPackagesInitial(it, PAGING_LIMIT) }

    })

    //Observe never called.  
    workPackagesViewModel.workPackagesList?.observe(this, Observer { wpList ->
        wpList ?: return@Observer

        adapter = WorkPackagesRecyclerAdapter(this)
        adapter.submitList(wpList)
        binding.workPackagesRecyclerView.adapter = adapter
        adapter.notifyDataSetChanged()

    })

As a test to my query, I've implemented:

val list = workPackageRepository.getAllWorkPackagesTestQuery() 

which returns 27 items, so query is fine. Am I setting up the the Dao wrong, the LivePagedListBuilder wrong? Why is observe not called?


Solution

  • You're not getting items because it's a PagedList. You need to trigger the load in order to obtain pages.

    That is why giving the PagedList to a PagedListAdapter via submitList will eventually load the data.

    You also don't need to manually invoke adapter.notifyDataSetChanged() when you're using a PagedListAdapter, because the DiffUtil will handle that internally.


    However, you should definitely be retrieving the DataSource.Factory and the LivePagedListBuilder and the PagedList on the UI thread (Dispatcher.MAIN), because threading is handled by the Paging lib's Room integration internally. Observing (and invoking getItem( on an unloaded element) will trigger the load, and the load will be executed asynchronously by the DataSource out of the box.



    The way to use Paging is like this:

    class MyViewModel(
        private val workPackageStorageDao: WorkPackageStorageDao
    ): ViewModel() {
        private val searchQuery: MutableLiveData<String> = MutableLiveData("")
    
        val workPackages: LiveData<PagedList<WorkPackage>> = Transformations.switchMap(searchQuery) { searchText ->
            val factory = workPackageStorageDao.searchWorkPackages(searchText, SortedBy.WorkPackageNumber)
            val pagedListBuilder = LivePagedListBuilder<Int, WorkPackage>(factory, pagingLimit)
            pagedListBuilder.build()
        }
    }
    

    Then in Fragment:

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    
        // ... setup RecyclerView, etc
    
        viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java, viewModelFactory)
        viewModel.workPackages.observe(viewLifecycleOwner, Observer { pagedList ->
            adapter.submitList(pagedList)
        })
    }
    

    And in adapter:

    class MyAdapter: PagedListAdapter<WorkPackage, MyAdapter.ViewHolder>(WorkPackage.ITEM_CALLBACK) {
         override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
             val workPackage = getItem(position)
             if(workPackage != null) {
                 holder.bind(workPackage)
             }
         }
    }
    

    Long story short, you don't need coroutines here. Use the Paging Library and LiveData, and it will load on the correct threads.