views-widgets-samples icon indicating copy to clipboard operation
views-widgets-samples copied to clipboard

ViewPager2 is not usable for fragment in androidx

Open ChillingVan opened this issue 4 years ago • 15 comments

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 avatar Jun 05 '20 02:06 ChillingVan

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
                }
            }
}

ChillingVan avatar Jun 05 '20 03:06 ChillingVan

mohmed1500 avatar Jul 10 '20 04:07 mohmed1500

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

kiquenet85 avatar Aug 14 '20 17:08 kiquenet85

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
    }

ChillingVan avatar Aug 15 '20 03:08 ChillingVan

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!

netcyrax avatar Aug 19 '20 14:08 netcyrax

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()
        }

ChillingVan avatar Aug 19 '20 14:08 ChillingVan

Thanks for replying! But still getting:

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

YourViewPagerAdapter is a FragmentStateAdapter?

netcyrax avatar Aug 19 '20 15:08 netcyrax

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

stargeraaas avatar Aug 19 '20 23:08 stargeraaas

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 avatar Aug 20 '20 02:08 ChillingVan

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.

ChillingVan avatar Aug 20 '20 02:08 ChillingVan

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!

netcyrax avatar Aug 20 '20 08:08 netcyrax

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

ChillingVan avatar Aug 24 '20 02:08 ChillingVan

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

KamaniAman avatar Aug 19 '21 06:08 KamaniAman

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

RandGor avatar Apr 15 '22 05:04 RandGor

it can be fixed by viewPager2.isSaveEnabled = false

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

bshakhatreh avatar Dec 18 '23 19:12 bshakhatreh