5

I like the approach of FragmentFactory, which makes it possible to pass dependencies to a fragment in a constructor.

I'd like to use a custom FragmentFactory together with a FragmentStatePagerAdapter.

I'm hosting the view pager in an activity. I can create a custom fragment factory and assign it to the activity using

supportFragmentManager.fragmentFactory = CustomFragmentFactory()

I can then use supportFragmentManager to initialize my FragmentStatePagerAdapter. That's fine so far.

My problem is that my fragments within the view pager show data dependent on the position.

Let's say I have a list items and ItemFragment, which is shown in a view pager showing items[position], hence have a constructor like this:

class ItemFragment(val item: Item) : Fragment()

How must I implement the CustomFragmentFactory and FragmentStatePagerAdapter's getItem(position: Int) function to achieve a safe equivalent of this:

override fun getItem(position: Int) = ItemFragment(items[position])
Peter F
  • 3,633
  • 3
  • 33
  • 45
  • 1
    Did you find a solution to this? I am facing the same issue.. – Andrew Jan 01 '21 at 15:52
  • Unfortunately, no. I didn't use the `FragmentFactory` approach at all but stuck with the traditional approach using empty constructor fragments passing arguments using `Bundle`. – Peter F Jan 03 '21 at 11:41

2 Answers2

0

First of all, FragmentStatePagerAdapter is deprecated now. So, it's better to use FragmentStateAdapter with ViewPager2.

I couldn't find any proper solution in case if you need to pass something into Fragment's constructor with ViewPager2 because it ignores FragmentFactory. I was thinking about implementing the FragmentFactory in createFragment method of FragmentStateAdapter but don't know where to get the proper class loader in this case.

The only solution I found is to use FragmentManager.FragmentLifecycleCallbacks. Here you have a fragment so you can cast it to you particular implementation. You can set callbacks or whatever you want to your fragment after its (re)creation. So in this case you don't need to pass something in to fragment's constructor but also can avoid creating Bundles.

fragmentManager?.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() {
        override fun onFragmentPreCreated(fm: FragmentManager, f: Fragment, savedInstanceState: Bundle?) {
            super.onFragmentPreCreated(fm, f, savedInstanceState)
        }
    }, true)

I tested this code using childFragmentManager and it works fine. But I have to admit that this is not a clear solution. And if you can avoid passing something to Fragment's constructor while using FragmentStateAdapter, avoid it. Because the most proper solution is to use a FragmentFactory.

Also, as an option, your fragment knows about the parent Activity. So, you can do callbacks directly to the parent Activity.

Link to documentation: https://developer.android.com/reference/androidx/fragment/app/FragmentManager#registerFragmentLifecycleCallbacks(androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks,%20boolean)

Nynuzoud
  • 101
  • 1
  • 3
  • I think we agree that `FragmentFactory` is not applicable here. I personally would not use the lifecycle callback approach to set arguments to a fragment because the need to do instance checks on the `Fragment` seems fragile. For passing arguments I'd keep the code in the fragment and also close to the constructor call, e.g. by using the newInstance pattern (https://medium.com/@azjkjensen/using-the-newinstance-pattern-in-kotlin-e40c1b4ba1ef) or Safe Args. – Peter F Jul 05 '21 at 14:34
  • @PeterF yep, I fully agree with you. I just provided a way how it is probably can be done if it is necessary. Because some of patterns that needs callbacks cannot be applied here because FragmentStateAdapter doesn't work with fragment factory. Also, I fully agree that class casting in this case is not a very clear way to solve it. And, as I said, if it is possible to avoid this solution, avoid it :) – Nynuzoud Jul 06 '21 at 17:04
0

FragmentFactory doesn't provide a way to pass dynamic data. Hence, FragmentFactorydoesn't work in this scenario.

If you have dynamic arguments you have to pass the them the classic way using a bundle (or use an approach like Safe Args, which generates code using a bundle).

This article talks about the exact same scenario:

The limitation of using FragmentFactory is that we can’t pass dynamic arguments to it.

Let’s say we have a ViewPager that display’s two instances of the same mainFragment but with different textToDisplay values then this won’t be possible using the fragmentFactory implementation.

Peter F
  • 3,633
  • 3
  • 33
  • 45