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