2

I am presenting a PagingSource returned by Room ORM on a PagingDataAdapter.

The RecyclerView is present on a Fragment -- I have two such fragments. When they are switched, they stop loading the items on next page and only placehodlers are shown on scrolling.

Please view these screen captures if it isn't clear what I mean--

Relevant pieces of code (please ask if you would like to see some other part/file) -

The Fragment:

private lateinit var recyclerView: RecyclerView
private val recyclerAdapter = CustomersAdapter(this)

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    recyclerView = view.findViewById(R.id.recycler_view)
    recyclerView.adapter = recyclerAdapter
    recyclerView.layoutManager = LinearLayoutManager(context)

    viewLifecycleOwner.lifecycleScope.launch {
        viewModel.customersFlow.collectLatest { pagingData ->
           recyclerAdapter.submitData(pagingData)
        }
    }
}

View model-

class CustomersListViewModel(application: Application, private val debtOnly: Boolean): ViewModel() {

    private val db = AppDatabase.instance(application)
    private val customersDao = db.customersDao()

    val customersFlow = Pager(PagingConfig(20)) {
        if (debtOnly)
            customersDao.getAllDebt()
        else
            customersDao.getAll()
    }.flow.cachedIn(viewModelScope)
}
Quanta
  • 594
  • 3
  • 24

1 Answers1

1

After I went through your code, I found the problem FragmentTransaction.replace function and flow.cachedIn(viewModelScope) When the activity calls the replace fragment function, the CustomerFragment will be destroyed and its ViewModel will also be destroyed (the viewModel.onCleared() is triggered) so this time cachedIn(viewModelScope) is also invalid.

I have 3 solutions for you

Solution 1: Remove .cachedIn(viewModelScope)

Note that this is only a temporary solution and is not recommended. Because of this, instances of fragments still exist on the activity but the fragments had destroyed (memory is still leaking).

Solution 2: Instead of using the FragmentTransaction.replace function in the Main activity, use the FragmentTransaction.add function:

It does not leak memory and can still use the cachedIn function. Should be used when the activity has few fragments and the fragment's view is not too complicated.

private fun switchNavigationFragment(navId: Int) {
    when (navId) {
        R.id.nav_customers -> {
            switchFragment(allCustomersFragment, "Customer")
        }
        R.id.nav_debt -> {
            switchFragment(debtCustomersFragment, "DebtCustomer")
        }
    }
}

private fun switchFragment(fragment: Fragment, tag: String) {
    val existingFragment = supportFragmentManager.findFragmentByTag(tag)
    supportFragmentManager.commit {
        supportFragmentManager.fragments.forEach {
            if (it.isVisible && it != fragment) {
                hide(it)
            }
        }
        if (existingFragment != fragment) {
            add(R.id.fragment_container, fragment, tag)
                .disallowAddToBackStack()
        } else {
            show(fragment)
        }
    }
}

Solution 3: Using with Navigation Component Jetpack

This is the safest solution. It can be created using Android Studio's template or some of the following articles.

Navigation UI

A safer way to collect flows

I tried solution 1 and 2 and here is the result:

The Result

Wilson Tran
  • 4,050
  • 3
  • 22
  • 31
  • Awesome! Thanks a lot for the detailed answer! I'll definitely try it out next morning, tired and down right now – Quanta May 30 '21 at 14:35
  • Neat. Can you please also point out in short why `disallowAddToBackStack()` call is required? – Quanta May 31 '21 at 07:28
  • Actually, without needing it, it still works properly, here is my code from an old project. You can see more at: https://developer.android.com/reference/android/app/FragmentTransaction#disallowAddToBackStack(). – Wilson Tran May 31 '21 at 07:33
  • I want to prevent some careless calling it to add to the fragment stack, when pressing back it will not return to the previous fragment but will exit the app. – Wilson Tran May 31 '21 at 07:40