Fixed
Status Update
Comments
[Deleted User] <[Deleted User]> #2
We have tests that ensure that onCreateView() is not called multiple times. If you can create a sample project that reproduces your issue, that would be most helpful.
[Deleted User] <[Deleted User]> #3
I can guarantee that onCreateView() is indeed being called twice, and there is an onDestroyView() in between. So there may be an issue with your tests.
Were you not able to reproduce the problem with a BottomNavigationView and a call to startPostponedTransition()?
Were you not able to reproduce the problem with a BottomNavigationView and a call to startPostponedTransition()?
jg...@google.com <jg...@google.com>
[Deleted User] <[Deleted User]> #4
No, I was not able to reproduce it.
jg...@google.com <jg...@google.com> #5
OK, I've created a sample project, reproducing the issue:
https://github.com/timusus/android-fragment-navigation-bug
I've noticed that this issue only occurs for the Fragment set as 'startDestination' in the navigation graph xml.
So to explain what the key players are:
1) Have a BottomNavigationView setup with NavController
2) Have a Fragment marked as the 'startDestination' in the navigation graph
3) Have that same Fragment perform postponeEnterTransition()
Also, to clarify. onCreateView() is not being called twice on the same Fragment instance, so I apologise - your tests may be fine. It appears there are two instances of the fragment in question.
I've noticed that this issue only occurs for the Fragment set as 'startDestination' in the navigation graph xml.
So to explain what the key players are:
1) Have a BottomNavigationView setup with NavController
2) Have a Fragment marked as the 'startDestination' in the navigation graph
3) Have that same Fragment perform postponeEnterTransition()
Also, to clarify. onCreateView() is not being called twice on the same Fragment instance, so I apologise - your tests may be fine. It appears there are two instances of the fragment in question.
jg...@google.com <jg...@google.com> #6
Thanks, your sample app was very helpful in reproducing what you saw. I was able to reproduce it by going to the Home fragment, then tapping on the Dashboard button to go back to the Dashboard fragment.
If you look at the NavigationUI code (https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.java#57 ), it does a few things when you click a MenuItem:
1) It adds the correct cross fade animations
2) It pops up to the start destination of your graph
3) It uses setLaunchSingleTop(true)
And then it navigates to the selected destination (i.e., the screen associated with the MenuItem you clicked) with the goal being that the user is at the exact correct location in the app.
Step 2) ensures that we don't keep an every growing stack. In your case, this pops the any instance of the Home or Notification fragment from the back stack. Step 3) ensures that there's only one instance of your start destination Dashboard fragment is on the back stack.
Why you're experiencing the behavior is due to the intersection of two things:
a) Fragments don't have a single top like functionality (there's no onNewIntent() like for an Activity), so FragmentNavigator, when faced with a single top operation like this one, pops the old instance and creates a new instance of the Fragment, passing through the new arguments, animations, etc.
b) As you've noticed, postponed Fragments are a special case that requires some extra work by FragmentManager. What happens is that the new Dashboard instance has its view created and becomes postponed. Since FragmentManager can't know when that will stop being the case, it basically reverses the replace() operation it was just about to do, causing the old Fragment to be eligible for having its view created. Therefore the second onCreateView() you see is the old instance having its view temporarily created to "fill the gap" until the new instance is available. After the new instance calls startPostponedEnterTransition(), the replace() is commited again and the old instance has its view removed.
From the Navigation perspective, it is indeed doing the right thing.
From the Fragment perspective, this case of pop+replace with a postponed Fragment is perhaps 'working as designed', but we're aware that the way that FragmentManager manages postponed Fragments is something that needs some improvement (to put it mildly). We have some ideas in this area, so I'll leave this issue open as a case we should specifically test to ensure it is fixed with any internal restructuring we do.
If you look at the NavigationUI code (
1) It adds the correct cross fade animations
2) It pops up to the start destination of your graph
3) It uses setLaunchSingleTop(true)
And then it navigates to the selected destination (i.e., the screen associated with the MenuItem you clicked) with the goal being that the user is at the exact correct location in the app.
Step 2) ensures that we don't keep an every growing stack. In your case, this pops the any instance of the Home or Notification fragment from the back stack. Step 3) ensures that there's only one instance of your start destination Dashboard fragment is on the back stack.
Why you're experiencing the behavior is due to the intersection of two things:
a) Fragments don't have a single top like functionality (there's no onNewIntent() like for an Activity), so FragmentNavigator, when faced with a single top operation like this one, pops the old instance and creates a new instance of the Fragment, passing through the new arguments, animations, etc.
b) As you've noticed, postponed Fragments are a special case that requires some extra work by FragmentManager. What happens is that the new Dashboard instance has its view created and becomes postponed. Since FragmentManager can't know when that will stop being the case, it basically reverses the replace() operation it was just about to do, causing the old Fragment to be eligible for having its view created. Therefore the second onCreateView() you see is the old instance having its view temporarily created to "fill the gap" until the new instance is available. After the new instance calls startPostponedEnterTransition(), the replace() is commited again and the old instance has its view removed.
From the Navigation perspective, it is indeed doing the right thing.
From the Fragment perspective, this case of pop+replace with a postponed Fragment is perhaps 'working as designed', but we're aware that the way that FragmentManager manages postponed Fragments is something that needs some improvement (to put it mildly). We have some ideas in this area, so I'll leave this issue open as a case we should specifically test to ensure it is fixed with any internal restructuring we do.
jg...@google.com <jg...@google.com> #7
Thanks for the detailed response Ian, I appreciate it.
I wonder if this problem would go away if I used the google sample code for bottom navigation, where it maintains multiple backstacks (maybe that eliminates a 'pop') . I might give that a try.
Keep up the great work.
I wonder if this problem would go away if I used the google sample code for bottom navigation, where it maintains multiple backstacks (maybe that eliminates a 'pop') . I might give that a try.
Keep up the great work.
jg...@google.com <jg...@google.com> #8
I'm facing the same problem.
When navigating to a fragment with postponeEnterTransition(), the start destination of the graph blinks before completing navigation to desired fragment.
Are you going to fix this?
When navigating to a fragment with postponeEnterTransition(), the start destination of the graph blinks before completing navigation to desired fragment.
Are you going to fix this?
[Deleted User] <[Deleted User]> #9
This has been fixed internally and will be avilable in Fragment 1.3.0-alpha08.
Note: this fix relies on using the
[Deleted User] <[Deleted User]> #10
Excellent. Thanks so much!
jg...@google.com <jg...@google.com> #11
I'm experiencing a similiar issue with version 1.3.1. I wrote some instrumented tests using androidx.fragment:fragment-testing:1.2.5 which are supposed to simulate Fragment being minimized (home button) and brought back again. I perform that by calling FragmentScenario.moveToState() to go RESUMED -> CREATED -> RESUMED. With 1.2.5 everything was fine, but after upgrade to 1.3.1 these tests started failing due to onCreateView() being called twice. I've described my problem with greater detail on SO https://stackoverflow.com/questions/66629817/testing-androidx-fragment-lifecycle-by-stopping-and-resuming-with-fragmentscenar .
This issue is already fixed in 1.3.1, so this probably is a problem in my configuration. Or maybe it's a regression ? It'd be helpful if someone experienced in this topic would take a look. I could provide sample project reproducing the issue on github if needed.
This issue is already fixed in 1.3.1, so this probably is a problem in my configuration. Or maybe it's a regression ? It'd be helpful if someone experienced in this topic would take a look. I could provide sample project reproducing the issue on github if needed.
[Deleted User] <[Deleted User]> #12
Re #11 - going from RESUMED
-> CREATED
will destroy your view (the CREATED
state is the state immediately after onCreate()
- i.e., before onCreateView()
), moving back to up RESUMED
will recreate the View. It sounds like Fragment 1.3.1 is working as intended.
jg...@google.com <jg...@google.com>
jg...@google.com <jg...@google.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()
}
}
```
[Deleted User] <[Deleted User]> #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!
[Deleted User] <[Deleted User]> #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)
}
}
})
}
}
ap...@google.com <ap...@google.com> #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
ap...@google.com <ap...@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
jg...@google.com <jg...@google.com> #18
beta04 with a fix for this was released today
Description
Creating Sample app to verify, but in case this is already a known issue