android / views-widgets-samples

Multiple samples showing the best practices in views-widgets on Android.
Apache License 2.0
5.04k stars 3.01k forks source link

ViewPager2 is not usable for fragment in androidx #139

Open ChillingVan opened 4 years ago

ChillingVan commented 4 years ago

In androidx, we use single Acitivty with multiple fragments for page transfer. When we use NavGraph to transfer A fragment (using ViewPager2) to B fragment, and then back from B fragment to A fragment. The A fragment will crash.

Here is the crash stack.

     java.lang.IllegalStateException: Expected the adapter to be 'fresh' while restoring state.
        at androidx.viewpager2.adapter.FragmentStateAdapter.restoreState(FragmentStateAdapter.java:536)
        at androidx.viewpager2.widget.ViewPager2.restorePendingState(ViewPager2.java:350)
        at androidx.viewpager2.widget.ViewPager2.dispatchRestoreInstanceState(ViewPager2.java:375)
        at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3886)
        at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3886)
        at android.view.View.restoreHierarchyState(View.java:19845)
        at androidx.fragment.app.Fragment.restoreViewState(Fragment.java:573)
        at androidx.fragment.app.FragmentStateManager.restoreViewState(FragmentStateManager.java:322)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1234)
        at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2390)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2125)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2081)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1977)
        at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:417)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:224)
        at android.app.ActivityThread.main(ActivityThread.java:7520)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
ChillingVan commented 4 years ago

And here is the code snippet:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
       mViewPager2.adapter = object : FragmentStateAdapter(this) {
                override fun createFragment(position: Int): Fragment {
                    return when (position) {
                        0 -> XXX1Fragment()
                        1 -> XXX2Fragment()
                        else -> XXX1Fragment()
                    }
                }

                override fun getItemCount(): Int {
                    return 2
                }
            }
}
mohmed1500 commented 4 years ago
kiquenet85 commented 3 years ago

Any update on this?, i'm having exactly the same problem. is there a workaround or how can we save the state correctly ?

ChillingVan commented 3 years ago

Any update on this?, i'm having exactly the same problem. is there a workaround or how can we save the state correctly ?

Hey, I found a solution. Caching the view created in onCreateView can work.

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        if (mRootView == null) {
            mRootView = inflater.inflate(getLayout(), container, false)
        } else {
            // remove the view from its parent
            mRootView?.removeSelf()
        }
        return mRootView
    }
netcyrax commented 3 years ago

Having the same issue but can't get this to work.

            // remove the view from its parent
            mRootView?.removeSelf()

There's no removeSelf() in View class. Also, where do you initiate your adapter?

Thanks!

ChillingVan commented 3 years ago

Having the same issue but can't get this to work.

            // remove the view from its parent
            mRootView?.removeSelf()

There's no removeSelf() in View class. Also, where do you initiate your adapter?

Thanks!

Hey. The removeSelf is:

inline fun View.removeSelf(): Boolean {
    if (this.parent != null && this.parent is ViewGroup) {
        (this.parent as ViewGroup).removeView(this)
        return true
    }
    return false
}

Init adapter in onViewCreated:

        if (mViewPager.adapter == null) {
            mViewPager.adapter = YourViewPagerAdapter(childFragmentManager,
                    listOf(
                            Fragment1.newInstance(),
                            Fragment2.newInstance()
                    ))
        } else {
            mViewPager.adapter?.notifyDataSetChanged()
        }
netcyrax commented 3 years ago

Thanks for replying! But still getting:

    java.lang.IllegalStateException: Expected the adapter to be 'fresh' while restoring state.

YourViewPagerAdapter is a FragmentStateAdapter?

stargeraaas commented 3 years ago

Are you tried to create this adapter only once time? Probably, these fragments are inflating every time in viewPager, when we get onViewCreated.

ChillingVan commented 3 years ago

Thanks for replying! But still getting:

    java.lang.IllegalStateException: Expected the adapter to be 'fresh' while restoring state.

YourViewPagerAdapter is a FragmentStateAdapter?

No, I don't know why FragmentStateAdapter not work either. I use FragmentPagerAdapter

ChillingVan commented 3 years ago

Are you tried to create this adapter only once time? Probably, these fragments are inflating every time in viewPager, when we get onViewCreated.

Yes, only create once. If you create it each time, there will be some minor issues. I donot quite remember what the issues are. It maybe the lifecycle issue that the not visible page, which is next to the visible one, will call onStart again if you create the adapter each time.

netcyrax commented 3 years ago

Thanks for replying! But still getting:

    java.lang.IllegalStateException: Expected the adapter to be 'fresh' while restoring state.

YourViewPagerAdapter is a FragmentStateAdapter?

No, I don't know why FragmentStateAdapter not work either. I use FragmentPagerAdapter

But you cannot use FragmentPagerAdapter with ViewPager2.

Thanks again!

ChillingVan commented 3 years ago

Thanks for replying! But still getting:

    java.lang.IllegalStateException: Expected the adapter to be 'fresh' while restoring state.

YourViewPagerAdapter is a FragmentStateAdapter?

No, I don't know why FragmentStateAdapter not work either. I use FragmentPagerAdapter

But you cannot use FragmentPagerAdapter with ViewPager2.

Thanks again! Oh, right. I saw the code of ViewPager instead of ViewPager2.. It seems that FragmentStateAdapter does not work, and there is no solution for now. https://stackoverflow.com/questions/56646711/expected-the-adapter-to-be-fresh-while-restoring-state

KamaniAman commented 2 years ago

I am also facing the same issue. Did you get any solution?

as a work around, I am just setting viewpager2 adapter to null onStop

but not sure is it a proper solution

RandGor commented 2 years ago

@KamaniAman did your solution works well for all this time or you are facing the same problem after applying it to app?

bshakhatreh commented 7 months ago

it can be fixed by viewPager2.isSaveEnabled = false

https://stackoverflow.com/a/63936638/2519297