[go: nahoru, domu]

Convert DialogFragmentNavigator to Navigator v2 APIs

Use NavigatorState as the source of truth for
DialogFragmentNavigator, switching to NavBackStackEntry
IDs as the tag for each DialogFragment.

Relnote: N/A
Test: updated DialogFragmentNavigatorTest tests pass
BUG: 187874376
Change-Id: I873fff1ec148956956238e3fabf8a781284423ca
diff --git a/navigation/navigation-fragment/api/current.ignore b/navigation/navigation-fragment/api/current.ignore
index bed10fd..b755bd3 100644
--- a/navigation/navigation-fragment/api/current.ignore
+++ b/navigation/navigation-fragment/api/current.ignore
@@ -3,6 +3,8 @@
     Method androidx.navigation.fragment.FragmentNavigator.Extras.getSharedElements has changed return type from java.util.Map<android.view.View!,java.lang.String!> to java.util.Map<android.view.View,java.lang.String>
 
 
+RemovedMethod: androidx.navigation.fragment.DialogFragmentNavigator#navigate(androidx.navigation.fragment.DialogFragmentNavigator.Destination, android.os.Bundle, androidx.navigation.NavOptions, androidx.navigation.Navigator.Extras):
+    Removed method androidx.navigation.fragment.DialogFragmentNavigator.navigate(androidx.navigation.fragment.DialogFragmentNavigator.Destination,android.os.Bundle,androidx.navigation.NavOptions,androidx.navigation.Navigator.Extras)
 RemovedMethod: androidx.navigation.fragment.DialogFragmentNavigator#popBackStack():
     Removed method androidx.navigation.fragment.DialogFragmentNavigator.popBackStack()
 RemovedMethod: androidx.navigation.fragment.FragmentNavigator#navigate(androidx.navigation.fragment.FragmentNavigator.Destination, android.os.Bundle, androidx.navigation.NavOptions, androidx.navigation.Navigator.Extras):
diff --git a/navigation/navigation-fragment/api/current.txt b/navigation/navigation-fragment/api/current.txt
index 5f8b262..faa9b59 100644
--- a/navigation/navigation-fragment/api/current.txt
+++ b/navigation/navigation-fragment/api/current.txt
@@ -12,7 +12,6 @@
   @androidx.navigation.Navigator.Name("dialog") public final class DialogFragmentNavigator extends androidx.navigation.Navigator<androidx.navigation.fragment.DialogFragmentNavigator.Destination> {
     ctor public DialogFragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager);
     method public androidx.navigation.fragment.DialogFragmentNavigator.Destination createDestination();
-    method public androidx.navigation.NavDestination? navigate(androidx.navigation.fragment.DialogFragmentNavigator.Destination destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
   }
 
   @androidx.navigation.NavDestination.ClassType(DialogFragment::class) public static class DialogFragmentNavigator.Destination extends androidx.navigation.NavDestination implements androidx.navigation.FloatingWindow {
diff --git a/navigation/navigation-fragment/api/public_plus_experimental_current.txt b/navigation/navigation-fragment/api/public_plus_experimental_current.txt
index 5f8b262..faa9b59 100644
--- a/navigation/navigation-fragment/api/public_plus_experimental_current.txt
+++ b/navigation/navigation-fragment/api/public_plus_experimental_current.txt
@@ -12,7 +12,6 @@
   @androidx.navigation.Navigator.Name("dialog") public final class DialogFragmentNavigator extends androidx.navigation.Navigator<androidx.navigation.fragment.DialogFragmentNavigator.Destination> {
     ctor public DialogFragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager);
     method public androidx.navigation.fragment.DialogFragmentNavigator.Destination createDestination();
-    method public androidx.navigation.NavDestination? navigate(androidx.navigation.fragment.DialogFragmentNavigator.Destination destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
   }
 
   @androidx.navigation.NavDestination.ClassType(DialogFragment::class) public static class DialogFragmentNavigator.Destination extends androidx.navigation.NavDestination implements androidx.navigation.FloatingWindow {
diff --git a/navigation/navigation-fragment/api/restricted_current.ignore b/navigation/navigation-fragment/api/restricted_current.ignore
index bed10fd..b755bd3 100644
--- a/navigation/navigation-fragment/api/restricted_current.ignore
+++ b/navigation/navigation-fragment/api/restricted_current.ignore
@@ -3,6 +3,8 @@
     Method androidx.navigation.fragment.FragmentNavigator.Extras.getSharedElements has changed return type from java.util.Map<android.view.View!,java.lang.String!> to java.util.Map<android.view.View,java.lang.String>
 
 
+RemovedMethod: androidx.navigation.fragment.DialogFragmentNavigator#navigate(androidx.navigation.fragment.DialogFragmentNavigator.Destination, android.os.Bundle, androidx.navigation.NavOptions, androidx.navigation.Navigator.Extras):
+    Removed method androidx.navigation.fragment.DialogFragmentNavigator.navigate(androidx.navigation.fragment.DialogFragmentNavigator.Destination,android.os.Bundle,androidx.navigation.NavOptions,androidx.navigation.Navigator.Extras)
 RemovedMethod: androidx.navigation.fragment.DialogFragmentNavigator#popBackStack():
     Removed method androidx.navigation.fragment.DialogFragmentNavigator.popBackStack()
 RemovedMethod: androidx.navigation.fragment.FragmentNavigator#navigate(androidx.navigation.fragment.FragmentNavigator.Destination, android.os.Bundle, androidx.navigation.NavOptions, androidx.navigation.Navigator.Extras):
diff --git a/navigation/navigation-fragment/api/restricted_current.txt b/navigation/navigation-fragment/api/restricted_current.txt
index 5f8b262..faa9b59 100644
--- a/navigation/navigation-fragment/api/restricted_current.txt
+++ b/navigation/navigation-fragment/api/restricted_current.txt
@@ -12,7 +12,6 @@
   @androidx.navigation.Navigator.Name("dialog") public final class DialogFragmentNavigator extends androidx.navigation.Navigator<androidx.navigation.fragment.DialogFragmentNavigator.Destination> {
     ctor public DialogFragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager);
     method public androidx.navigation.fragment.DialogFragmentNavigator.Destination createDestination();
-    method public androidx.navigation.NavDestination? navigate(androidx.navigation.fragment.DialogFragmentNavigator.Destination destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
   }
 
   @androidx.navigation.NavDestination.ClassType(DialogFragment::class) public static class DialogFragmentNavigator.Destination extends androidx.navigation.NavDestination implements androidx.navigation.FloatingWindow {
diff --git a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/DialogFragmentNavigatorTest.kt b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/DialogFragmentNavigatorTest.kt
index c66710b..39bc126 100644
--- a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/DialogFragmentNavigatorTest.kt
+++ b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/DialogFragmentNavigatorTest.kt
@@ -23,8 +23,10 @@
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.FragmentFactory
 import androidx.fragment.app.FragmentManager
+import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavController
 import androidx.navigation.Navigation
+import androidx.navigation.testing.TestNavigatorState
 import androidx.test.annotation.UiThreadTest
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
@@ -34,6 +36,7 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import kotlin.reflect.KClass
 
 @LargeTest
 @RunWith(AndroidJUnit4::class)
@@ -49,11 +52,16 @@
 
     private lateinit var emptyActivity: EmptyActivity
     private lateinit var fragmentManager: FragmentManager
+    private lateinit var navigatorState: TestNavigatorState
+    private lateinit var dialogNavigator: DialogFragmentNavigator
 
     @Before
     fun setup() {
         emptyActivity = activityRule.activity
         fragmentManager = emptyActivity.supportFragmentManager
+        navigatorState = TestNavigatorState()
+        dialogNavigator = DialogFragmentNavigator(emptyActivity, fragmentManager)
+        dialogNavigator.onAttach(navigatorState)
     }
 
     @UiThreadTest
@@ -69,14 +77,11 @@
                 }
             }
         }
-        val dialogNavigator = DialogFragmentNavigator(emptyActivity, fragmentManager)
-        val destination = dialogNavigator.createDestination().apply {
-            id = INITIAL_FRAGMENT
-            setClassName(EmptyDialogFragment::class.java.name)
-        }
+        val entry = createBackStackEntry()
 
-        assertThat(dialogNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        dialogNavigator.navigate(listOf(entry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
         fragmentManager.executePendingTransactions()
         assertWithMessage("Dialog should be shown")
             .that(dialogFragment.requireDialog().isShowing)
@@ -118,25 +123,34 @@
                 }
             }
         }
-        val dialogNavigator = DialogFragmentNavigator(emptyActivity, fragmentManager)
-        val destination = dialogNavigator.createDestination().apply {
-            id = INITIAL_FRAGMENT
-            setClassName(EmptyDialogFragment::class.java.name)
-        }
+        val entry = createBackStackEntry()
 
-        assertThat(dialogNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        dialogNavigator.navigate(listOf(entry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
         fragmentManager.executePendingTransactions()
         assertWithMessage("Dialog should be shown")
             .that(dialogFragment.requireDialog().isShowing)
             .isTrue()
+        dialogNavigator.popBackStack(entry, false)
         assertWithMessage("DialogNavigator should pop dialog off the back stack")
-            .that(dialogNavigator.popBackStack())
-            .isTrue()
+            .that(navigatorState.backStack.value)
+            .isEmpty()
         assertWithMessage("Pop should dismiss the DialogFragment")
             .that(dialogFragment.requireDialog().isShowing)
             .isFalse()
     }
+
+    private fun createBackStackEntry(
+        destId: Int = INITIAL_FRAGMENT,
+        clazz: KClass<out Fragment> = EmptyDialogFragment::class
+    ): NavBackStackEntry {
+        val destination = dialogNavigator.createDestination().apply {
+            id = destId
+            setClassName(clazz.java.name)
+        }
+        return navigatorState.createBackStackEntry(destination, null)
+    }
 }
 
 class EmptyDialogFragment : DialogFragment() {
diff --git a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.kt b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.kt
index e584d40..1f6792a 100644
--- a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.kt
+++ b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.kt
@@ -16,7 +16,6 @@
 package androidx.navigation.fragment
 
 import android.content.Context
-import android.os.Bundle
 import android.util.AttributeSet
 import android.util.Log
 import androidx.annotation.CallSuper
@@ -26,11 +25,12 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
 import androidx.navigation.FloatingWindow
+import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavDestination
 import androidx.navigation.NavOptions
 import androidx.navigation.Navigator
-import androidx.navigation.NavigatorState
 import androidx.navigation.NavigatorProvider
+import androidx.navigation.NavigatorState
 import androidx.navigation.fragment.DialogFragmentNavigator.Destination
 
 /**
@@ -43,8 +43,7 @@
     private val context: Context,
     private val fragmentManager: FragmentManager
 ) : Navigator<Destination>() {
-    private var dialogCount = 0
-    private val restoredTagsAwaitingAttach = mutableSetOf<String?>()
+    private val restoredTagsAwaitingAttach = mutableSetOf<String>()
     private val observer = LifecycleEventObserver { source, event ->
         if (event == Lifecycle.Event.ON_STOP) {
             val dialogFragment = source as DialogFragment
@@ -54,38 +53,53 @@
         }
     }
 
-    public override fun popBackStack(): Boolean {
-        if (dialogCount == 0) {
-            return false
-        }
+    override fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) {
         if (fragmentManager.isStateSaved) {
             Log.i(
                 TAG, "Ignoring popBackStack() call: FragmentManager has already saved its state"
             )
-            return false
+            return
         }
-        val existingFragment = fragmentManager.findFragmentByTag(DIALOG_TAG + --dialogCount)
-        if (existingFragment != null) {
-            existingFragment.lifecycle.removeObserver(observer)
-            (existingFragment as DialogFragment).dismiss()
+        val beforePopList = state.backStack.value
+        // Get the set of entries that are going to be popped
+        val poppedList = beforePopList.subList(
+            beforePopList.indexOf(popUpTo),
+            beforePopList.size
+        )
+        // Now go through the list in reversed order (i.e., starting from the most recently added)
+        // and dismiss each dialog
+        for (entry in poppedList.reversed()) {
+            val existingFragment = fragmentManager.findFragmentByTag(entry.id)
+            if (existingFragment != null) {
+                existingFragment.lifecycle.removeObserver(observer)
+                (existingFragment as DialogFragment).dismiss()
+            }
         }
-        return true
+        state.pop(popUpTo, savedState)
     }
 
     public override fun createDestination(): Destination {
         return Destination(this)
     }
 
-    public override fun navigate(
-        destination: Destination,
-        args: Bundle?,
+    override fun navigate(
+        entries: List<NavBackStackEntry>,
         navOptions: NavOptions?,
         navigatorExtras: Extras?
-    ): NavDestination? {
+    ) {
         if (fragmentManager.isStateSaved) {
             Log.i(TAG, "Ignoring navigate() call: FragmentManager has already saved its state")
-            return null
+            return
         }
+        for (entry in entries) {
+            navigate(entry)
+        }
+    }
+
+    private fun navigate(
+        entry: NavBackStackEntry
+    ) {
+        val destination = entry.destination as Destination
         var className = destination.className
         if (className[0] == '.') {
             className = context.packageName + className
@@ -97,33 +111,20 @@
             "Dialog destination ${destination.className} is not an instance of DialogFragment"
         }
         val dialogFragment = frag as DialogFragment
-        dialogFragment.arguments = args
+        dialogFragment.arguments = entry.arguments
         dialogFragment.lifecycle.addObserver(observer)
-        dialogFragment.show(fragmentManager, DIALOG_TAG + dialogCount++)
-        return destination
-    }
-
-    public override fun onSaveState(): Bundle? {
-        if (dialogCount == 0) {
-            return null
-        }
-        val b = Bundle()
-        b.putInt(KEY_DIALOG_COUNT, dialogCount)
-        return b
-    }
-
-    public override fun onRestoreState(savedState: Bundle) {
-        dialogCount = savedState.getInt(KEY_DIALOG_COUNT, 0)
-        for (index in 0 until dialogCount) {
-            val fragment = fragmentManager
-                .findFragmentByTag(DIALOG_TAG + index) as DialogFragment?
-            fragment?.lifecycle?.addObserver(observer)
-                ?: restoredTagsAwaitingAttach.add(DIALOG_TAG + index)
-        }
+        dialogFragment.show(fragmentManager, entry.id)
+        state.add(entry)
     }
 
     override fun onAttach(state: NavigatorState) {
         super.onAttach(state)
+        for (entry in state.backStack.value) {
+            val fragment = fragmentManager
+                .findFragmentByTag(entry.id) as DialogFragment?
+            fragment?.lifecycle?.addObserver(observer)
+                ?: restoredTagsAwaitingAttach.add(entry.id)
+        }
         fragmentManager.addFragmentOnAttachListener { _, childFragment ->
             val needToAddObserver = restoredTagsAwaitingAttach.remove(childFragment.tag)
             if (needToAddObserver) {
@@ -195,7 +196,5 @@
 
     private companion object {
         private const val TAG = "DialogFragmentNavigator"
-        private const val KEY_DIALOG_COUNT = "androidx-nav-dialogfragment:navigator:count"
-        private const val DIALOG_TAG = "androidx-nav-fragment:navigator:dialog:"
     }
 }