Merge "FragmentTransactionCallback: state saving callback" into androidx-main
diff --git a/libraryversions.toml b/libraryversions.toml
index 8c64828..1b6b1c7 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -118,7 +118,7 @@
VECTORDRAWABLE_SEEKABLE = "1.0.0-beta02"
VERSIONED_PARCELABLE = "1.2.0-alpha01"
VIEWPAGER = "1.1.0-alpha02"
-VIEWPAGER2 = "1.1.0-beta02"
+VIEWPAGER2 = "1.2.0-alpha01"
WEAR = "1.3.0-alpha03"
WEAR_COMPOSE = "1.0.0-beta03"
WEAR_INPUT = "1.2.0-alpha03"
diff --git a/viewpager2/viewpager2/api/current.txt b/viewpager2/viewpager2/api/current.txt
index 997ce2a..111eb2f 100644
--- a/viewpager2/viewpager2/api/current.txt
+++ b/viewpager2/viewpager2/api/current.txt
@@ -24,6 +24,7 @@
method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentMaxLifecyclePreUpdated(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State);
method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreAdded(androidx.fragment.app.Fragment);
method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreRemoved(androidx.fragment.app.Fragment);
+ method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreSavedInstanceState(androidx.fragment.app.Fragment);
}
public static interface FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener {
diff --git a/viewpager2/viewpager2/api/public_plus_experimental_current.txt b/viewpager2/viewpager2/api/public_plus_experimental_current.txt
index 997ce2a..111eb2f 100644
--- a/viewpager2/viewpager2/api/public_plus_experimental_current.txt
+++ b/viewpager2/viewpager2/api/public_plus_experimental_current.txt
@@ -24,6 +24,7 @@
method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentMaxLifecyclePreUpdated(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State);
method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreAdded(androidx.fragment.app.Fragment);
method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreRemoved(androidx.fragment.app.Fragment);
+ method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreSavedInstanceState(androidx.fragment.app.Fragment);
}
public static interface FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener {
diff --git a/viewpager2/viewpager2/api/restricted_current.txt b/viewpager2/viewpager2/api/restricted_current.txt
index 4aab4bd..9316cd0 100644
--- a/viewpager2/viewpager2/api/restricted_current.txt
+++ b/viewpager2/viewpager2/api/restricted_current.txt
@@ -24,6 +24,7 @@
method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentMaxLifecyclePreUpdated(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State);
method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreAdded(androidx.fragment.app.Fragment);
method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreRemoved(androidx.fragment.app.Fragment);
+ method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreSavedInstanceState(androidx.fragment.app.Fragment);
}
public static interface FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener {
diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt
index 7a85e17..eca228f 100644
--- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt
+++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt
@@ -225,6 +225,82 @@
}
@Test
+ fun test_fragmentSaveSateCallback() {
+ setUpTest(ORIENTATION_HORIZONTAL).apply {
+ // given
+ val items = stringSequence(10).toMutableList()
+ val adapterProvider = fragmentAdapterProviderValueId.provider(items)
+ setAdapterSync(adapterProvider)
+ runOnUiThreadSync { viewPager.offscreenPageLimit = 1 }
+
+ val adapter = viewPager.adapter as FragmentStateAdapter
+ val fragmentManager = activity.supportFragmentManager
+
+ val log = RecordingLogger()
+ val adapterCallback = createRecordingFragmentTransactionCallback(log)
+ val lifecycleCallback = createRecordingFragmentLifecycleCallback(log)
+ adapter.registerFragmentTransactionCallback(adapterCallback)
+ fragmentManager.registerFragmentLifecycleCallbacks(lifecycleCallback, true)
+
+ // when: new item is pushed at the start of the list
+ val latch = adapter.registerFragmentRemovedLatch()
+
+ runOnUiThreadSync {
+ val newValue = "11"
+ assertThat(items.contains(newValue), equalTo(false))
+ items.add(0, newValue) // note: new value added at the start of the list
+
+ adapter.notifyDataSetChanged()
+ }
+
+ latch.awaitStrict(5)
+
+ // then
+ assertThat(
+ log.consume(), equalTo(
+ listOf(
+ "Adapter:onFragmentPreAdded(<no-tag>)",
+ "Lifecycle:onFragmentPreAttached(f1)",
+ "Lifecycle:onFragmentAttached(f1)",
+ "Lifecycle:onFragmentPreCreated(f1)",
+ "Lifecycle:onFragmentCreated(f1)",
+ "Lifecycle:onFragmentViewCreated(f1)",
+ "Lifecycle:onFragmentActivityCreated(f1)",
+ "Lifecycle:onFragmentStarted(f1)",
+ "Adapter:onFragmentAdded(f1)",
+ "Adapter:onFragmentPreAdded(<no-tag>)",
+ "Lifecycle:onFragmentPreAttached(f11)",
+ "Lifecycle:onFragmentAttached(f11)",
+ "Lifecycle:onFragmentPreCreated(f11)",
+ "Lifecycle:onFragmentCreated(f11)",
+ "Lifecycle:onFragmentViewCreated(f11)",
+ "Lifecycle:onFragmentActivityCreated(f11)",
+ "Lifecycle:onFragmentStarted(f11)",
+ "Adapter:onFragmentMaxLifecyclePreUpdated(f0 at STARTED)",
+ "Adapter:onFragmentMaxLifecyclePreUpdated(f1 at STARTED)",
+ "Adapter:onFragmentMaxLifecyclePreUpdated(f11 at RESUMED)",
+ "Lifecycle:onFragmentPaused(f0)",
+ "Lifecycle:onFragmentResumed(f11)",
+ "Adapter:onFragmentMaxLifecycleUpdated(f11 at RESUMED)",
+ "Adapter:onFragmentMaxLifecycleUpdated(f1 at STARTED)",
+ "Adapter:onFragmentMaxLifecycleUpdated(f0 at STARTED)",
+ "Adapter:onFragmentAdded(f11)",
+ "Adapter:onFragmentPreSavedInstanceState(f1)", // note: pre-saved
+ "Lifecycle:onFragmentSaveInstanceState(f1)",
+ "Adapter:onFragmentSavedInstanceState(f1)", // note: saved
+ "Adapter:onFragmentPreRemoved(f1)",
+ "Lifecycle:onFragmentStopped(f1)",
+ "Lifecycle:onFragmentViewDestroyed(f1)",
+ "Lifecycle:onFragmentDestroyed(f1)",
+ "Lifecycle:onFragmentDetached(f1)",
+ "Adapter:onFragmentRemoved(<no-tag>)",
+ )
+ )
+ )
+ }
+ }
+
+ @Test
fun test_unregisterInsideCallback() {
setUpTest(ORIENTATION_HORIZONTAL).apply {
setAdapterSync(fragmentAdapterProviderValueId.provider(stringSequence(2)))
@@ -360,6 +436,15 @@
}
}
+ override fun onFragmentPreSavedInstanceState(
+ fragment: Fragment
+ ): OnPostEventListener {
+ log.append("Adapter:onFragmentPreSavedInstanceState(${fragment.name})")
+ return OnPostEventListener {
+ log.append("Adapter:onFragmentSavedInstanceState(${fragment.name})")
+ }
+ }
+
override fun onFragmentPreRemoved(fragment: Fragment): OnPostEventListener {
log.append("Adapter:onFragmentPreRemoved(${fragment.name})")
return OnPostEventListener {
diff --git a/viewpager2/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java b/viewpager2/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
index 2c77b9b..a0ec29c 100644
--- a/viewpager2/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
+++ b/viewpager2/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
@@ -474,7 +474,12 @@
}
if (fragment.isAdded() && containsItem(itemId)) {
- mSavedStates.put(itemId, mFragmentManager.saveFragmentInstanceState(fragment));
+ List<OnPostEventListener> onPost =
+ mFragmentEventDispatcher.dispatchPreSavedInstanceState(fragment);
+ Fragment.SavedState savedState = mFragmentManager.saveFragmentInstanceState(fragment);
+ mFragmentEventDispatcher.dispatchPostEvents(onPost);
+
+ mSavedStates.put(itemId, savedState);
}
List<OnPostEventListener> onPost =
mFragmentEventDispatcher.dispatchPreRemoved(fragment);
@@ -837,6 +842,14 @@
return result;
}
+ public List<OnPostEventListener> dispatchPreSavedInstanceState(Fragment fragment) {
+ List<OnPostEventListener> result = new ArrayList<>();
+ for (FragmentTransactionCallback callback : mCallbacks) {
+ result.add(callback.onFragmentPreSavedInstanceState(fragment));
+ }
+ return result;
+ }
+
public List<OnPostEventListener> dispatchPreRemoved(Fragment fragment) {
List<OnPostEventListener> result = new ArrayList<>();
for (FragmentTransactionCallback callback : mCallbacks) {
@@ -870,6 +883,18 @@
}
/**
+ * Called right before Fragment's state is being saved through a
+ * {@link FragmentManager#saveFragmentInstanceState} call.
+ *
+ * @param fragment Fragment which state is being saved
+ * @return Listener called after the operation
+ */
+ @NonNull
+ public OnPostEventListener onFragmentPreSavedInstanceState(@NonNull Fragment fragment) {
+ return NO_OP;
+ }
+
+ /**
* Called right before the Fragment is removed from adapter's FragmentManager.
*
* @param fragment Fragment changing state