I've spent a bit of time with this, and I've diagnosed the problem for anyone who needs to hear it. I tried to keep my solution as conventional as possible. If we look at your statement:
Therefore the adapter will be recreated when I navigate back to the
HomeFragment, which also recreates all Fragments in the Viewpager2 and
the current item is reset to 0.
The problem is that the current item is reset to 0, because the list that your adapter is based off-of is recreated. To resolve the issue, we don't need to save the adapter, just the data inside of it. With that in mind, solving the problem is not difficult at all.
Let's layout some definitions:
HomeFragment
is, as you've said, the host of your ViewPager2
,
MainActivity
is the running activity which hosts HomeFragment
and all created fragments inside of it
- We are paging through instances of
MyFragment
. You could even have more than one type of fragment that you page through, but that's beyond the scope of this example.
PagerAdapter
is your FragmentStateAdapter
, which is the adapter for HomeFragment
's ViewPager2
.
In this example, MyFragment
has the constructor constructor(id : Int)
. Then, PagerAdapter
is probably going to appear as follows:
class PagerAdapter(fm : Fragment) : FragmentStateAdapter(fm){
var ids : List<Int> = listOf()
...
override fun createFragment(position : Int) : Fragment{
return MyFragment(ids[position])
}
}
The problem that we are facing is every time you recreate PagerAdapter
the constructor is called and that constructor, as we can see above, sets ids
to an empty list.
My first thought was that maybe I could switch fm
to be MainActivity
. I don't navigate out of MainActivity
so I'm not sure why, but this solution doesn't work.
Instead, what you need to do is abstract the data out of PagerAdapter
. Create a "viewModel":
/* We do NOT extend ViewModel. This naming just indicates that this is your data-
storage vehicle for PagerAdapter*/
data class PagerAdapterViewModel(
var ids : List<Int>
)
Then, in PagerAdapter
, make the following adjustments:
class PagerAdapter(
fm : Fragment,
private val viewModel : PagerAdapterViewModel
) : FragmentStateAdapter(fm){
// by creating custom getters and setters, you are migrating your code to this
// implementation without needing to adjust any code outside of the adapter
var ids : List<Int>
get() = viewModel.ids
set(value) {viewModel.ids = value}
override fun createFragment(position : Int) : Fragment{
return MyFragment(ids[position])
}
}
Finally, in HomeFragment
, you'll have something like:
class HomeFragment : Fragment(){
...
/** Calling "by lazy" ensures that this object is only created once, and hence
we retain the data stored in it, even when navigating away. */
private val pagerAdapterViewModel : PagerAdapterViewModel by lazy{
PagerAdapterViewModel(listOf())
}
private lateinit var pagerAdapter : PagerAdapter
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
pagerAdapter = PagerAdapter(this, pagerAdapterViewModel)
pager.adapter = pagerAdapter
...
}
...
}
notifyDataSetChanged()
inside my databinding bindingadapter and that will be called everytime the view is created as the livedata of the viewmodel is re-attached. You need to check if the data has changed and only then callnotifyDataSetChanged()
as this will recreate all fragments. I will also have to try to useDiffUtil
for this – TriennialviewPager.offscreenPageLimit = 3
and current item save it status – Draculabeta05
and looks like it's fine now – TitanatesetOffscreenPageLimit(length of total pages);
is this not working? – Apropos