Fixed
Status Update
Comments
jg...@google.com <jg...@google.com> #2
Found the issue, ugh
Noticed that when I do the following:
transaction.addToBackStack(null)
When adding a Fragment with ViewPager2, the above issue occurs, although, idk if it's becaugh of gcFragments() anymore.
As soon as I un-comment that line, the issue disappears.
Noticed that when I do the following:
transaction.addToBackStack(null)
When adding a Fragment with ViewPager2, the above issue occurs, although, idk if it's becaugh of gcFragments() anymore.
As soon as I un-comment that line, the issue disappears.
h6...@gmail.com <h6...@gmail.com> #3
Is the Sample application, to reproduce, move back really fast between page 2 -> 1 and vice versa (they are bottom buttons) a couple times.
Wait a couple seconds and page 1 (the black page) should turn white meaning the Fragment has been removed.
As soon as you comment line 40 on MainActivity, and attempt again, the error does not persist
h6...@gmail.com <h6...@gmail.com> #4
Woops, sorry, If you guys had the previous ViewPagerCrash test app, I forgot to push (was exhasuted after debugging) I pushed one more time for the latest reproducible code
h6...@gmail.com <h6...@gmail.com> #5
We'll have a look, thanks for reporting and repro steps / sample app. Stay tuned for updates on this issue.
h6...@gmail.com <h6...@gmail.com> #6
Got to debug this now. Your sample indeed showcases an issue, so thanks for reporting and for the sample!
`transaction.addToBackStack(null)` adds the Fragment replacement transaction to the back stack, so you can go back to the previous state with the back button. Is that what you were looking to achieve? You said it works without it, which might help in the meantime.
Would you mind describing the use-case, so I better understand what you're trying to achieve? E.g. in the example, by switching between buttons 1 and 2 at some point you achieve multiple stack entries going back to the ViewPager2 Fragment.
`transaction.addToBackStack(null)` adds the Fragment replacement transaction to the back stack, so you can go back to the previous state with the back button. Is that what you were looking to achieve? You said it works without it, which might help in the meantime.
Would you mind describing the use-case, so I better understand what you're trying to achieve? E.g. in the example, by switching between buttons 1 and 2 at some point you achieve multiple stack entries going back to the ViewPager2 Fragment.
jg...@google.com <jg...@google.com> #7
Mostly for my notes, but the trace below explains what happens. Still need to work out why.
We have 5 instances of FragmentStateAdapter below.
Notice how in lines 36-39 4 different instances of FragmentStateAdapter perform a scheduled gc (after grace period expires).
Those don't have the Fragments bound to their ViewHolders as those were removed: lines 8, 16, 24, 32.
At the same time, com.test.myapplication.VPAdapter.createFragment(ViewPagerFragment.kt:161) only gets called once, so presumably all 5 have a reference to that same Fragment.
Needs more investigation as looks fishy.
1 {FragmentStateAdapter@11,288}.mItemIdToViewHolder will be modified at FragmentStateAdapter.<init>(FragmentStateAdapter.java:85). Current value = 'null'. New value = 'instance of androidx.collection.LongSparseArray(id=11289)'
2 {FragmentStateAdapter@11,288}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
3 {FragmentStateAdapter@11,288}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onBindViewHolder(FragmentStateAdapter.java:174)
4 {FragmentStateAdapter@11,288}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
5 {FragmentStateAdapter@11,288}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:235)
6 {FragmentStateAdapter@11,288}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:240)
7 {FragmentStateAdapter@11,288}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
8 {FragmentStateAdapter@11,288}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onViewRecycled(FragmentStateAdapter.java:391)
9 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be modified at FragmentStateAdapter.<init>(FragmentStateAdapter.java:85). Current value = 'null'. New value = 'instance of androidx.collection.LongSparseArray(id=11307)'
10 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
11 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onBindViewHolder(FragmentStateAdapter.java:174)
12 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
13 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:235)
14 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:240)
15 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
16 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onViewRecycled(FragmentStateAdapter.java:391)
17 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be modified at FragmentStateAdapter.<init>(FragmentStateAdapter.java:85). Current value = 'null'. New value = 'instance of androidx.collection.LongSparseArray(id=11314)'
18 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
19 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onBindViewHolder(FragmentStateAdapter.java:174)
20 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
21 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:235)
22 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:240)
23 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
24 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onViewRecycled(FragmentStateAdapter.java:391)
25 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be modified at FragmentStateAdapter.<init>(FragmentStateAdapter.java:85). Current value = 'null'. New value = 'instance of androidx.collection.LongSparseArray(id=11316)'
26 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
27 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onBindViewHolder(FragmentStateAdapter.java:174)
28 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
29 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:235)
30 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:240)
31 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
32 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onViewRecycled(FragmentStateAdapter.java:391)
33 {FragmentStateAdapter@11,317}.mItemIdToViewHolder will be modified at FragmentStateAdapter.<init>(FragmentStateAdapter.java:85). Current value = 'null'. New value = 'instance of androidx.collection.LongSparseArray(id=11318)'
34 {FragmentStateAdapter@11,317}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
35 {FragmentStateAdapter@11,317}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onBindViewHolder(FragmentStateAdapter.java:174)
36 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.gcFragments(FragmentStateAdapter.java:221)
37 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.gcFragments(FragmentStateAdapter.java:221)
38 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.gcFragments(FragmentStateAdapter.java:221)
39 {FragmentStateAdapter@11,317}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.gcFragments(FragmentStateAdapter.java:221)
We have 5 instances of FragmentStateAdapter below.
Notice how in lines 36-39 4 different instances of FragmentStateAdapter perform a scheduled gc (after grace period expires).
Those don't have the Fragments bound to their ViewHolders as those were removed: lines 8, 16, 24, 32.
At the same time, com.test.myapplication.VPAdapter.createFragment(ViewPagerFragment.kt:161) only gets called once, so presumably all 5 have a reference to that same Fragment.
Needs more investigation as looks fishy.
1 {FragmentStateAdapter@11,288}.mItemIdToViewHolder will be modified at FragmentStateAdapter.<init>(FragmentStateAdapter.java:85). Current value = 'null'. New value = 'instance of androidx.collection.LongSparseArray(id=11289)'
2 {FragmentStateAdapter@11,288}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
3 {FragmentStateAdapter@11,288}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onBindViewHolder(FragmentStateAdapter.java:174)
4 {FragmentStateAdapter@11,288}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
5 {FragmentStateAdapter@11,288}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:235)
6 {FragmentStateAdapter@11,288}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:240)
7 {FragmentStateAdapter@11,288}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
8 {FragmentStateAdapter@11,288}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onViewRecycled(FragmentStateAdapter.java:391)
9 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be modified at FragmentStateAdapter.<init>(FragmentStateAdapter.java:85). Current value = 'null'. New value = 'instance of androidx.collection.LongSparseArray(id=11307)'
10 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
11 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onBindViewHolder(FragmentStateAdapter.java:174)
12 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
13 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:235)
14 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:240)
15 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
16 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onViewRecycled(FragmentStateAdapter.java:391)
17 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be modified at FragmentStateAdapter.<init>(FragmentStateAdapter.java:85). Current value = 'null'. New value = 'instance of androidx.collection.LongSparseArray(id=11314)'
18 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
19 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onBindViewHolder(FragmentStateAdapter.java:174)
20 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
21 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:235)
22 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:240)
23 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
24 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onViewRecycled(FragmentStateAdapter.java:391)
25 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be modified at FragmentStateAdapter.<init>(FragmentStateAdapter.java:85). Current value = 'null'. New value = 'instance of androidx.collection.LongSparseArray(id=11316)'
26 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
27 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onBindViewHolder(FragmentStateAdapter.java:174)
28 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
29 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:235)
30 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:240)
31 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
32 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onViewRecycled(FragmentStateAdapter.java:391)
33 {FragmentStateAdapter@11,317}.mItemIdToViewHolder will be modified at FragmentStateAdapter.<init>(FragmentStateAdapter.java:85). Current value = 'null'. New value = 'instance of androidx.collection.LongSparseArray(id=11318)'
34 {FragmentStateAdapter@11,317}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.itemForViewHolder(FragmentStateAdapter.java:234)
35 {FragmentStateAdapter@11,317}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.onBindViewHolder(FragmentStateAdapter.java:174)
36 {FragmentStateAdapter@11,306}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.gcFragments(FragmentStateAdapter.java:221)
37 {FragmentStateAdapter@11,313}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.gcFragments(FragmentStateAdapter.java:221)
38 {FragmentStateAdapter@11,315}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.gcFragments(FragmentStateAdapter.java:221)
39 {FragmentStateAdapter@11,317}.mItemIdToViewHolder will be accessed at FragmentStateAdapter.gcFragments(FragmentStateAdapter.java:221)
jg...@google.com <jg...@google.com>
ap...@google.com <ap...@google.com> #9
Yes, completely correct that I am attempting to use the back pressed implementation but also a bottom navigation implementation to switch to and from Fragments.
Currently the app that I am trying to make contains multiple Fragments with and without ViewPager2's accessible through a bottom navigation set of buttons. Through simple usage of moving to and from my ViewPager2 Fragments, I would see random instances of child Fragments disappearing from them.
Currently the app that I am trying to make contains multiple Fragments with and without ViewPager2's accessible through a bottom navigation set of buttons. Through simple usage of moving to and from my ViewPager2 Fragments, I would see random instances of child Fragments disappearing from them.
ap...@google.com <ap...@google.com> #10
I'd rather like to keep the "addToBackStack(..)" to rewind through fragment transactions. Will definitely look further into it also from the above mentioned logs.
jg...@google.com <jg...@google.com> #11
Thanks for clarifying, I will investigate further.
Following on the logs above, I suspect that each instance of FragmentStateAdapter gets access to that one BaseFragment in FragmentStateAdapter::restoreState (restore state called on each stack entry).
Making sure each ViewPagerFragment instance only gets access to its own state (so the state is not shared between them) should eliminate this issue.
Not sure where the problem lies yet, whether in ViewPager2's interaction with the state saving stack, or in the sample app, so continuing to investigate.
Following on the logs above, I suspect that each instance of FragmentStateAdapter gets access to that one BaseFragment in FragmentStateAdapter::restoreState (restore state called on each stack entry).
Making sure each ViewPagerFragment instance only gets access to its own state (so the state is not shared between them) should eliminate this issue.
Not sure where the problem lies yet, whether in ViewPager2's interaction with the state saving stack, or in the sample app, so continuing to investigate.
jg...@google.com <jg...@google.com> #12
Yeah.. I have no clue what's going on, spending too much time debugging. I am planning to attach LifecycleObserver to my ViewHolder's so they act like Fragments.
From the evidence above, I am pretty sure it should not affect ViewHolder pattern but never know. Would love to know what was happening though if you come across a solution.
From the evidence above, I am pretty sure it should not affect ViewHolder pattern but never know. Would love to know what was happening though if you come across a solution.
wa...@gmail.com <wa...@gmail.com> #13
Looks like the following is happening in the sample:
- the same instance of ViewPagerFragment is being used for various back-stack entries
- multiple instances of FragmentStateAdapter are being created at each ViewPagerFragment view inflation, and each has their post-grace-period gcFragments() scheduled
- the same page Fragment instance reference is passed to all FragmentStateAdapter instances via restoreState, but can only be bound to one ViewHolder (the current visible one)
- once grace-period ends in non-visible FragmentStateAdapter instances, they see nothing is bound to their ViewHolders and garbage-collect the page Fragment
I will look further into the correct solution, potentially cancelling gcFragments() if a FragmentStateAdapter gets detached from a ViewPager2 (e.g. at ViewPagerFragment view destruction).
This would require a code, change, though, so for now, you can probably work around this by not re-using ViewPagerFragment instances, but creating a new one each time you add it to the stack. There might be a need for passing relevant state, e.g. current page.
Raw debugging notes below in case helpful. I reproduced the issue by switching multiple times between buttons 1 and 2.
***
# how many adapter instances exist
️$ cat ff2 | grep -E "FragmentStateAdapter@[^\}]+" -o | sort -u
FragmentStateAdapter@5,446
FragmentStateAdapter@5,474
FragmentStateAdapter@5,484
# how many adapter instances get their state restored
️$ cat ff2 | grep restoreState | grep -E "FragmentStateAdapter@[^\}]+" -o | sort -u
FragmentStateAdapter@5,474
FragmentStateAdapter@5,484
# where are Fragment pages created
️$ cat ff2 | grep createFragment -C1
{androidx.viewpager2.adapter.FragmentStateAdapter@5,446}.mFragments will be accessed at androidx.viewpager2.adapter.FragmentStateAdapter.ensureFragment(FragmentStateAdapter.java:248)
Breakpoint reached at com.test.myapplication.VPAdapter.createFragment(ViewPagerFragment.kt:161)
{androidx.viewpager2.adapter.FragmentStateAdapter@5,446}.mFragments will be accessed at androidx.viewpager2.adapter.FragmentStateAdapter.ensureFragment(FragmentStateAdapter.java:252)
# confirming that instance 446 is the only adapter creating the views; others are getting it from previously restored state
# for reference, FragmentStateAdapter.java:248 = "if (!mFragments.containsKey(itemId)) {"
# for reference, FragmentStateAdapter.java:252 = "mFragments.put(itemId, newFragment);"
️$ cat ff2 | grep ensureFragment | sort
{androidx.viewpager2.adapter.FragmentStateAdapter@5,446}.mFragments will be accessed at androidx.viewpager2.adapter.FragmentStateAdapter.ensureFragment(FragmentStateAdapter.java:248)
{androidx.viewpager2.adapter.FragmentStateAdapter@5,446}.mFragments will be accessed at androidx.viewpager2.adapter.FragmentStateAdapter.ensureFragment(FragmentStateAdapter.java:252)
{androidx.viewpager2.adapter.FragmentStateAdapter@5,474}.mFragments will be accessed at androidx.viewpager2.adapter.FragmentStateAdapter.ensureFragment(FragmentStateAdapter.java:248)
{androidx.viewpager2.adapter.FragmentStateAdapter@5,484}.mFragments will be accessed at androidx.viewpager2.adapter.FragmentStateAdapter.ensureFragment(FragmentStateAdapter.java:248)
# where is the state with a page Fragment reference coming from
Breakpoint reached
at androidx.viewpager2.adapter.FragmentStateAdapter.restoreState(FragmentStateAdapter.java:515)
at androidx.viewpager2.widget.ViewPager2.restorePendingState(ViewPager2.java:337)
at androidx.viewpager2.widget.ViewPager2.dispatchRestoreInstanceState(ViewPager2.java:362)
at android.view.View.restoreHierarchyState(View.java:17684)
at androidx.fragment.app.Fragment.restoreViewState(Fragment.java:539)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:907)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1238)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1303)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:439)
at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2076)
at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1866)
at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1821)
at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:1727)
at androidx.fragment.app.FragmentManagerImpl$2.run(FragmentManagerImpl.java:150)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Method.java:-1)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
# how many Fragment instances go through restoreViewState
️$ cat ff4 | grep "restoreViewState. fragment="
restoreViewState: fragment=ViewPagerFragment{d4490de (a6cfe7d3-5376-4015-8357-2408442116ee) id=0x7f070076 Frag1}
restoreViewState: fragment=BaseFragment{de52066 (f5afa133-40d7-45c1-8193-6ce2d4cb5489) f0}
restoreViewState: fragment=BaseFragment{de52066 (f5afa133-40d7-45c1-8193-6ce2d4cb5489) f0}
restoreViewState: fragment=ViewPagerFragment{d4490de (a6cfe7d3-5376-4015-8357-2408442116ee) id=0x7f070076 Frag1}
restoreViewState: fragment=BaseFragment{de52066 (f5afa133-40d7-45c1-8193-6ce2d4cb5489) f0}
restoreViewState: fragment=ViewPagerFragment{d4490de (a6cfe7d3-5376-4015-8357-2408442116ee) id=0x7f070076 Frag1}
# where are ViewPagerFragment instances created in the sample
```
var fragment: Fragment? = supportFragmentManager.findFragmentByTag("Frag$fragmentToOpen")
if(fragment == null) {
fragment = when(fragmentToOpen) {
1 -> ViewPagerFragment()
2 -> BlankFragment()
else -> throw IllegalArgumentException()
}
}
```
- the same instance of ViewPagerFragment is being used for various back-stack entries
- multiple instances of FragmentStateAdapter are being created at each ViewPagerFragment view inflation, and each has their post-grace-period gcFragments() scheduled
- the same page Fragment instance reference is passed to all FragmentStateAdapter instances via restoreState, but can only be bound to one ViewHolder (the current visible one)
- once grace-period ends in non-visible FragmentStateAdapter instances, they see nothing is bound to their ViewHolders and garbage-collect the page Fragment
I will look further into the correct solution, potentially cancelling gcFragments() if a FragmentStateAdapter gets detached from a ViewPager2 (e.g. at ViewPagerFragment view destruction).
This would require a code, change, though, so for now, you can probably work around this by not re-using ViewPagerFragment instances, but creating a new one each time you add it to the stack. There might be a need for passing relevant state, e.g. current page.
Raw debugging notes below in case helpful. I reproduced the issue by switching multiple times between buttons 1 and 2.
***
# how many adapter instances exist
️$ cat ff2 | grep -E "FragmentStateAdapter@[^\}]+" -o | sort -u
FragmentStateAdapter@5,446
FragmentStateAdapter@5,474
FragmentStateAdapter@5,484
# how many adapter instances get their state restored
️$ cat ff2 | grep restoreState | grep -E "FragmentStateAdapter@[^\}]+" -o | sort -u
FragmentStateAdapter@5,474
FragmentStateAdapter@5,484
# where are Fragment pages created
️$ cat ff2 | grep createFragment -C1
{androidx.viewpager2.adapter.FragmentStateAdapter@5,446}.mFragments will be accessed at androidx.viewpager2.adapter.FragmentStateAdapter.ensureFragment(FragmentStateAdapter.java:248)
Breakpoint reached at com.test.myapplication.VPAdapter.createFragment(ViewPagerFragment.kt:161)
{androidx.viewpager2.adapter.FragmentStateAdapter@5,446}.mFragments will be accessed at androidx.viewpager2.adapter.FragmentStateAdapter.ensureFragment(FragmentStateAdapter.java:252)
# confirming that instance 446 is the only adapter creating the views; others are getting it from previously restored state
# for reference, FragmentStateAdapter.java:248 = "if (!mFragments.containsKey(itemId)) {"
# for reference, FragmentStateAdapter.java:252 = "mFragments.put(itemId, newFragment);"
️$ cat ff2 | grep ensureFragment | sort
{androidx.viewpager2.adapter.FragmentStateAdapter@5,446}.mFragments will be accessed at androidx.viewpager2.adapter.FragmentStateAdapter.ensureFragment(FragmentStateAdapter.java:248)
{androidx.viewpager2.adapter.FragmentStateAdapter@5,446}.mFragments will be accessed at androidx.viewpager2.adapter.FragmentStateAdapter.ensureFragment(FragmentStateAdapter.java:252)
{androidx.viewpager2.adapter.FragmentStateAdapter@5,474}.mFragments will be accessed at androidx.viewpager2.adapter.FragmentStateAdapter.ensureFragment(FragmentStateAdapter.java:248)
{androidx.viewpager2.adapter.FragmentStateAdapter@5,484}.mFragments will be accessed at androidx.viewpager2.adapter.FragmentStateAdapter.ensureFragment(FragmentStateAdapter.java:248)
# where is the state with a page Fragment reference coming from
Breakpoint reached
at androidx.viewpager2.adapter.FragmentStateAdapter.restoreState(FragmentStateAdapter.java:515)
at androidx.viewpager2.widget.ViewPager2.restorePendingState(ViewPager2.java:337)
at androidx.viewpager2.widget.ViewPager2.dispatchRestoreInstanceState(ViewPager2.java:362)
at android.view.View.restoreHierarchyState(View.java:17684)
at androidx.fragment.app.Fragment.restoreViewState(Fragment.java:539)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:907)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1238)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1303)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:439)
at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2076)
at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1866)
at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1821)
at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:1727)
at androidx.fragment.app.FragmentManagerImpl$2.run(FragmentManagerImpl.java:150)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Method.java:-1)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
# how many Fragment instances go through restoreViewState
️$ cat ff4 | grep "restoreViewState. fragment="
restoreViewState: fragment=ViewPagerFragment{d4490de (a6cfe7d3-5376-4015-8357-2408442116ee) id=0x7f070076 Frag1}
restoreViewState: fragment=BaseFragment{de52066 (f5afa133-40d7-45c1-8193-6ce2d4cb5489) f0}
restoreViewState: fragment=BaseFragment{de52066 (f5afa133-40d7-45c1-8193-6ce2d4cb5489) f0}
restoreViewState: fragment=ViewPagerFragment{d4490de (a6cfe7d3-5376-4015-8357-2408442116ee) id=0x7f070076 Frag1}
restoreViewState: fragment=BaseFragment{de52066 (f5afa133-40d7-45c1-8193-6ce2d4cb5489) f0}
restoreViewState: fragment=ViewPagerFragment{d4490de (a6cfe7d3-5376-4015-8357-2408442116ee) id=0x7f070076 Frag1}
# where are ViewPagerFragment instances created in the sample
```
var fragment: Fragment? = supportFragmentManager.findFragmentByTag("Frag$fragmentToOpen")
if(fragment == null) {
fragment = when(fragmentToOpen) {
1 -> ViewPagerFragment()
2 -> BlankFragment()
else -> throw IllegalArgumentException()
}
}
```
jg...@google.com <jg...@google.com> #14
Thanks for the suggestion above, creating a new Fragment every time definitely fixes the problem albeit some very ugly code implementation and hacks I had to write to do some state saving in my code (since ViewModels are attached to a specific Fragment instance), my subsequent fragments also need to do some ugly state saving (playing media).
Looking forward to erasing all of this ugly code on next update!
Looking forward to erasing all of this ugly code on next update!
wa...@gmail.com <wa...@gmail.com> #15
For anybody who has the same exact problem, page state saving works for me in the following manner:
binding.gamePagerRoot.apply {
// Set the Adapter for our Fragment
adapter = gamePagerAdapter
// Do not allow user touch for this ViewPager2 to affect its scroll, should only be scrolled programmatically
isUserInputEnabled = false
// Set the Page Transformation
setPageTransformer(genStackedPageTransformer2())
// Listen to page change calbacks
registerOnPageChangeCallback(onPageChangeCallback)
// Customize the RecyclerView child
val recyclerView = getChildAt(0) as RecyclerView
// Remove the overscroll shaddow
recyclerView.overScrollMode = RecyclerView.OVER_SCROLL_NEVER
// Allow nested scrolling
recyclerView.isNestedScrollingEnabled = true
// If we are on a different page as specified by our ViewModel, make sure to scroll to the page instantly (no animation)
if(gamePagerVM?.gamePageIds?.size ?: 1 > 1) {
recyclerView.viewTreeObserver.addOnGlobalLayoutListener(object: ViewTreeObserver.OnGlobalLayoutListener{
override fun onGlobalLayout() {
if(recyclerView.childCount >= gamePagerVM?.currPage ?: 0) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
// If the current page is not 0, set the current page
setCurrentItem(gamePagerVM?.currPage ?: 0, false)
}
}
})
}
}
binding.gamePagerRoot.apply {
// Set the Adapter for our Fragment
adapter = gamePagerAdapter
// Do not allow user touch for this ViewPager2 to affect its scroll, should only be scrolled programmatically
isUserInputEnabled = false
// Set the Page Transformation
setPageTransformer(genStackedPageTransformer2())
// Listen to page change calbacks
registerOnPageChangeCallback(onPageChangeCallback)
// Customize the RecyclerView child
val recyclerView = getChildAt(0) as RecyclerView
// Remove the overscroll shaddow
recyclerView.overScrollMode = RecyclerView.OVER_SCROLL_NEVER
// Allow nested scrolling
recyclerView.isNestedScrollingEnabled = true
// If we are on a different page as specified by our ViewModel, make sure to scroll to the page instantly (no animation)
if(gamePagerVM?.gamePageIds?.size ?: 1 > 1) {
recyclerView.viewTreeObserver.addOnGlobalLayoutListener(object: ViewTreeObserver.OnGlobalLayoutListener{
override fun onGlobalLayout() {
if(recyclerView.childCount >= gamePagerVM?.currPage ?: 0) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
// If the current page is not 0, set the current page
setCurrentItem(gamePagerVM?.currPage ?: 0, false)
}
}
})
}
}
[Deleted User] <[Deleted User]> #16
Project: platform/frameworks/support
Branch: androidx-master-dev
commit 7166ba571d594baa9016e02c3b5fe967da0b8ec0
Author: Jakub Gielzak <jgielzak@google.com>
Date: Tue Aug 27 16:20:54 2019
Fix for FragmentStateAdapter#gcFragments()
Fix for an issue with FragmentStateAdapter garbage collection mechanism.
The bug manifested itself in the following scenario:
- ViewPager2 host Fragment view recreated 3+ times in a window < 10s,
- Host Fragment instances added to the back stack,
- ViewPager2 adapter set to null in onViewDestoyed (too late as view
state already saved, but affecting PageFragment book-keeping).
The fix adds another criteria to gcFragments() in case a
PageFragment instance gets taken over by a later created instance of
FragmentStateAdapter.
Bug: 139095195
Test: ./gradlew viewpager2:connectedCheck
Change-Id: If42bc4e65e8fe52cc0dcd9bbf67a0c55d3c535b1
A viewpager2/src/androidTest/java/androidx/viewpager2/widget/HostFragmentBackStackTest.kt
M viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
https://android-review.googlesource.com/1108545
https://goto.google.com/android-sha1/7166ba571d594baa9016e02c3b5fe967da0b8ec0
Branch: androidx-master-dev
commit 7166ba571d594baa9016e02c3b5fe967da0b8ec0
Author: Jakub Gielzak <jgielzak@google.com>
Date: Tue Aug 27 16:20:54 2019
Fix for FragmentStateAdapter#gcFragments()
Fix for an issue with FragmentStateAdapter garbage collection mechanism.
The bug manifested itself in the following scenario:
- ViewPager2 host Fragment view recreated 3+ times in a window < 10s,
- Host Fragment instances added to the back stack,
- ViewPager2 adapter set to null in onViewDestoyed (too late as view
state already saved, but affecting PageFragment book-keeping).
The fix adds another criteria to gcFragments() in case a
PageFragment instance gets taken over by a later created instance of
FragmentStateAdapter.
Bug: 139095195
Test: ./gradlew viewpager2:connectedCheck
Change-Id: If42bc4e65e8fe52cc0dcd9bbf67a0c55d3c535b1
A viewpager2/src/androidTest/java/androidx/viewpager2/widget/HostFragmentBackStackTest.kt
M viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
jg...@google.com <jg...@google.com> #17
Project: platform/frameworks/support
Branch: androidx-master-dev
commit 39daf73f75d04b1ba52b007781355595430f8dab
Author: Jakub Gielzak <jgielzak@google.com>
Date: Tue Aug 27 16:20:16 2019
Made test FragmentAdapter more flexible.
Needed for the next CL.
Bug: 139095195
Test: ./gradlew viewpager2:connectedCheck
Change-Id: I3aa4b8a131d3d7bca248f94641c5ee643c6f1819
M viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
M viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/FragmentAdapter.kt
https://android-review.googlesource.com/1108544
https://goto.google.com/android-sha1/39daf73f75d04b1ba52b007781355595430f8dab
Branch: androidx-master-dev
commit 39daf73f75d04b1ba52b007781355595430f8dab
Author: Jakub Gielzak <jgielzak@google.com>
Date: Tue Aug 27 16:20:16 2019
Made test FragmentAdapter more flexible.
Needed for the next CL.
Bug: 139095195
Test: ./gradlew viewpager2:connectedCheck
Change-Id: I3aa4b8a131d3d7bca248f94641c5ee643c6f1819
M viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
M viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/FragmentAdapter.kt
ye...@tokopedia.com <ye...@tokopedia.com> #18
beta04 with a fix for this was released today
ye...@tokopedia.com <ye...@tokopedia.com> #19
ch...@caravan.gg <ch...@caravan.gg> #20
Occurred in 1.0.0
java.lang.IllegalStateException: Design assumption violated.
at androidx.viewpager2.adapter.FragmentStateAdapter.placeFragmentInViewHolder(FragmentStateAdapter.java:287)
at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:276)
at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:67)
at androidx.recyclerview.widget.RecyclerView.dispatchChildAttached(RecyclerView.java:7556)
at androidx.recyclerview.widget.RecyclerView$5.addView(RecyclerView.java:860)
at androidx.recyclerview.widget.ChildHelper.addView(ChildHelper.java:107)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addViewInt(RecyclerView.java:8601)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:8559)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:8547)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1641)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at androidx.viewpager2.widget.ViewPager2.onLayout(ViewPager2.java:527)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at com.google.android.material.appbar.HeaderScrollingViewBehavior.layoutChild(HeaderScrollingViewBehavior.java:148)
at com.google.android.material.appbar.ViewOffsetBehavior.onLayoutChild(ViewOffsetBehavior.java:43)
at com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior.onLayoutChild(AppBarLayout.java:1892)
at androidx.coordinatorlayout.widget.CoordinatorLayout.onLayout(CoordinatorLayout.java:918)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1915)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at androidx.drawerlayout.widget.DrawerLayout.onLayout(DrawerLayout.java:1231)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:573)
at android.widget.FrameLayout.onLayout(FrameLayout.java:508)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1703)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1557)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1466)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:573)
at android.widget.FrameLayout.onLayout(FrameLayout.java:508)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1703)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1557)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1466)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:573)
at android.widget.FrameLayout.onLayout(FrameLayout.java:508)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:
java.lang.IllegalStateException: Design assumption violated.
at androidx.viewpager2.adapter.FragmentStateAdapter.placeFragmentInViewHolder(FragmentStateAdapter.java:287)
at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:276)
at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:67)
at androidx.recyclerview.widget.RecyclerView.dispatchChildAttached(RecyclerView.java:7556)
at androidx.recyclerview.widget.RecyclerView$5.addView(RecyclerView.java:860)
at androidx.recyclerview.widget.ChildHelper.addView(ChildHelper.java:107)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addViewInt(RecyclerView.java:8601)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:8559)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:8547)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1641)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at androidx.viewpager2.widget.ViewPager2.onLayout(ViewPager2.java:527)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at com.google.android.material.appbar.HeaderScrollingViewBehavior.layoutChild(HeaderScrollingViewBehavior.java:148)
at com.google.android.material.appbar.ViewOffsetBehavior.onLayoutChild(ViewOffsetBehavior.java:43)
at com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior.onLayoutChild(AppBarLayout.java:1892)
at androidx.coordinatorlayout.widget.CoordinatorLayout.onLayout(CoordinatorLayout.java:918)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1915)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at androidx.drawerlayout.widget.DrawerLayout.onLayout(DrawerLayout.java:1231)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:573)
at android.widget.FrameLayout.onLayout(FrameLayout.java:508)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1703)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1557)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1466)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:573)
at android.widget.FrameLayout.onLayout(FrameLayout.java:508)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1703)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1557)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1466)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:573)
at android.widget.FrameLayout.onLayout(FrameLayout.java:508)
at android.view.View.layout(View.java:15596)
at android.view.ViewGroup.layout(ViewGroup.java:4966)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:
b4...@gmail.com <b4...@gmail.com> #21
version 1.0.0-beta03
java.lang.IllegalStateException: Design assumption violated.
at androidx.viewpager2.adapter.FragmentStateAdapter.placeFragmentInViewHolder(FragmentStateAdapter.java:287)
at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:276)
at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:67)
at androidx.recyclerview.widget.RecyclerView.dispatchChildAttached(RecyclerView.java:7867)
at androidx.recyclerview.widget.RecyclerView$5.addView(RecyclerView.java:893)
at androidx.recyclerview.widget.ChildHelper.addView(ChildHelper.java:107)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addViewInt(RecyclerView.java:8902)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:8860)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:8848)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1645)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1591)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:668)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep1(RecyclerView.java:4255)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:4010)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4578)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at androidx.viewpager2.widget.ViewPager2.onLayout(ViewPager2.java:527)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at com.android.internal.policy.DecorView.onLayout(DecorView.java:804)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3686)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3144)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2115)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8582)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:975)
at android.view.Choreographer.doCallbacks(Choreographer.java:799)
at android.view.Choreographer.doFrame(Choreographer.java:734)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:960)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:236)
at android.app.ActivityThread.main(ActivityThread.java:7864)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:620)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1011)
java.lang.IllegalStateException: Design assumption violated.
at androidx.viewpager2.adapter.FragmentStateAdapter.placeFragmentInViewHolder(FragmentStateAdapter.java:287)
at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:276)
at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:67)
at androidx.recyclerview.widget.RecyclerView.dispatchChildAttached(RecyclerView.java:7867)
at androidx.recyclerview.widget.RecyclerView$5.addView(RecyclerView.java:893)
at androidx.recyclerview.widget.ChildHelper.addView(ChildHelper.java:107)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addViewInt(RecyclerView.java:8902)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:8860)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:8848)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1645)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1591)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:668)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep1(RecyclerView.java:4255)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:4010)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4578)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at androidx.viewpager2.widget.ViewPager2.onLayout(ViewPager2.java:527)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at com.android.internal.policy.DecorView.onLayout(DecorView.java:804)
at android.view.View.layout(View.java:23219)
at android.view.ViewGroup.layout(ViewGroup.java:6532)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3686)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3144)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2115)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8582)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:975)
at android.view.Choreographer.doCallbacks(Choreographer.java:799)
at android.view.Choreographer.doFrame(Choreographer.java:734)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:960)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:236)
at android.app.ActivityThread.main(ActivityThread.java:7864)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:620)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1011)
ly...@gmail.com <ly...@gmail.com> #22
The cause is getItemId return the duplicate id.
I find self test stable reproduction.
I find self test stable reproduction.
Description
- androidx.viewpager2:viewpager2:1.0.0-alpha04
I noticed that ViewPager2 sometimes crashes with the following stack trace. Currently, I have not figured out how to reproduce the error frequently, so I will attach the procedure/sample project for reproduction later.
```
java.lang.IllegalStateException: Design assumption violated.
at androidx.viewpager2.adapter.FixedFragmentStateAdapter.placeFragmentInViewHolder(FixedFragmentStateAdapter.java:234)
at androidx.viewpager2.adapter.FixedFragmentStateAdapter.onViewAttachedToWindow(FixedFragmentStateAdapter.java:223)
at androidx.viewpager2.adapter.FixedFragmentStateAdapter.onViewAttachedToWindow(FixedFragmentStateAdapter.java:61)
at androidx.recyclerview.widget.RecyclerView.dispatchChildAttached(RecyclerView.java:7433)
at androidx.recyclerview.widget.RecyclerView$5.addView(RecyclerView.java:862)
at androidx.recyclerview.widget.ChildHelper.addView(ChildHelper.java:107)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addViewInt(RecyclerView.java:8478)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:8436)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1643)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
at androidx.recyclerview.widget.LinearLayoutManager.scrollBy(LinearLayoutManager.java:1391)
at androidx.recyclerview.widget.LinearLayoutManager.scrollHorizontallyBy(LinearLayoutManager.java:1116)
at androidx.recyclerview.widget.RecyclerView.scrollStep(RecyclerView.java:1840)
at androidx.recyclerview.widget.RecyclerView.scrollByInternal(RecyclerView.java:1942)
at androidx.recyclerview.widget.RecyclerView.onTouchEvent(RecyclerView.java:3297)
at androidx.viewpager2.widget.ViewPager2$RecyclerViewImpl.onTouchEvent(ViewPager2.java:797)
at android.view.View.dispatchTouchEvent(View.java:12513)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3024)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2705)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:440)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1830)
at android.app.Activity.dispatchTouchEvent(Activity.java:3400)
at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69)
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:398)
at android.view.View.dispatchPointerEvent(View.java:12752)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:5106)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4909)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4585)
```