aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Lake <ilake@google.com>2019-04-24 15:37:02 -0700
committerIan Lake <ilake@google.com>2019-04-25 15:41:55 -0700
commitd5a1c250e6e3471297f64e116a46d1a3a5c9db6c (patch)
tree1db65893105d62fb924fd660378d8a0c405cfa42
parenta831c66f1be7a98d259e5bf123d656c87b9a0e87 (diff)
downloadsupport-d5a1c250e6e3471297f64e116a46d1a3a5c9db6c.tar.gz
Use OnBackPressedCallback in NavController
Replace the brittle OnBackStackChangedListener with an OnBackPressedCallback registered at the NavController level which works on all Navigators. It also helps fix a timing issue where the state of the FragmentNavigator isn't updated after a Fragment is popped off the stack, leading to a mismatch in expected actions available if the newly visible Fragment calls navigate() in a lifecycle method. Test: updated tests, new OnBackPressedTest BUG: 111598096 Change-Id: Id679ea6ac9b238240649656fc0796552d6191757
-rw-r--r--navigation/common/api/restricted_2.1.0-alpha03.txt16
-rw-r--r--navigation/common/api/restricted_current.txt16
-rw-r--r--navigation/common/src/main/java/androidx/navigation/Navigator.java66
-rw-r--r--navigation/fragment/src/androidTest/AndroidManifest.xml1
-rw-r--r--navigation/fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt130
-rw-r--r--navigation/fragment/src/androidTest/java/androidx/navigation/fragment/OnBackPressedTest.kt105
-rw-r--r--navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/EmptyFragment.kt2
-rw-r--r--navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/NavigationActivity.kt24
-rw-r--r--navigation/fragment/src/androidTest/res/navigation/nav_simple.xml31
-rw-r--r--navigation/fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.java99
-rw-r--r--navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java4
-rw-r--r--navigation/runtime/api/2.1.0-alpha03.txt2
-rw-r--r--navigation/runtime/api/current.txt2
-rw-r--r--navigation/runtime/build.gradle1
-rw-r--r--navigation/runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt46
-rw-r--r--navigation/runtime/src/main/java/androidx/navigation/NavController.java101
-rw-r--r--navigation/runtime/src/main/java/androidx/navigation/NavHost.java14
17 files changed, 254 insertions, 406 deletions
diff --git a/navigation/common/api/restricted_2.1.0-alpha03.txt b/navigation/common/api/restricted_2.1.0-alpha03.txt
index 37d70c254db..da4f6cc18fe 100644
--- a/navigation/common/api/restricted_2.1.0-alpha03.txt
+++ b/navigation/common/api/restricted_2.1.0-alpha03.txt
@@ -1,17 +1 @@
// Signature format: 3.0
-package androidx.navigation {
-
- public abstract class Navigator<D extends androidx.navigation.NavDestination> {
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void addOnNavigatorBackPressListener(androidx.navigation.Navigator.OnNavigatorBackPressListener);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void dispatchOnNavigatorBackPress();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected void onBackPressAdded();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected void onBackPressRemoved();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void removeOnNavigatorBackPressListener(androidx.navigation.Navigator.OnNavigatorBackPressListener);
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static interface Navigator.OnNavigatorBackPressListener {
- method public void onPopBackStack(androidx.navigation.Navigator);
- }
-
-}
-
diff --git a/navigation/common/api/restricted_current.txt b/navigation/common/api/restricted_current.txt
index 37d70c254db..da4f6cc18fe 100644
--- a/navigation/common/api/restricted_current.txt
+++ b/navigation/common/api/restricted_current.txt
@@ -1,17 +1 @@
// Signature format: 3.0
-package androidx.navigation {
-
- public abstract class Navigator<D extends androidx.navigation.NavDestination> {
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void addOnNavigatorBackPressListener(androidx.navigation.Navigator.OnNavigatorBackPressListener);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void dispatchOnNavigatorBackPress();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected void onBackPressAdded();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected void onBackPressRemoved();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void removeOnNavigatorBackPressListener(androidx.navigation.Navigator.OnNavigatorBackPressListener);
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static interface Navigator.OnNavigatorBackPressListener {
- method public void onPopBackStack(androidx.navigation.Navigator);
- }
-
-}
-
diff --git a/navigation/common/src/main/java/androidx/navigation/Navigator.java b/navigation/common/src/main/java/androidx/navigation/Navigator.java
index 979e8d6d0d9..46627005005 100644
--- a/navigation/common/src/main/java/androidx/navigation/Navigator.java
+++ b/navigation/common/src/main/java/androidx/navigation/Navigator.java
@@ -25,11 +25,9 @@ import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
-import java.util.concurrent.CopyOnWriteArrayList;
/**
* Navigator defines a mechanism for navigating within an app.
@@ -67,9 +65,6 @@ public abstract class Navigator<D extends NavDestination> {
String value();
}
- private final CopyOnWriteArrayList<OnNavigatorBackPressListener> mOnBackPressListeners =
- new CopyOnWriteArrayList<>();
-
/**
* Construct a new NavDestination associated with this Navigator.
*
@@ -133,67 +128,6 @@ public abstract class Navigator<D extends NavDestination> {
}
/**
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- protected void onBackPressAdded() {
- }
-
- /**
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- protected void onBackPressRemoved() {
- }
-
- /**
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public final void addOnNavigatorBackPressListener(
- @NonNull OnNavigatorBackPressListener listener) {
- boolean added = mOnBackPressListeners.add(listener);
- if (added && mOnBackPressListeners.size() == 1) {
- onBackPressAdded();
- }
- }
-
- /**
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public final void removeOnNavigatorBackPressListener(
- @NonNull OnNavigatorBackPressListener listener) {
- boolean removed = mOnBackPressListeners.remove(listener);
- if (removed && mOnBackPressListeners.isEmpty()) {
- onBackPressRemoved();
- }
- }
-
- /**
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public final void dispatchOnNavigatorBackPress() {
- for (OnNavigatorBackPressListener listener : mOnBackPressListeners) {
- listener.onPopBackStack(this);
- }
- }
-
- /**
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public interface OnNavigatorBackPressListener {
- /**
- * This method is called after the Navigator navigates to a new destination.
- *
- * @param navigator
- */
- void onPopBackStack(@NonNull Navigator navigator);
- }
-
- /**
* Interface indicating that this class should be passed to its respective
* {@link Navigator} to enable Navigator specific behavior.
*/
diff --git a/navigation/fragment/src/androidTest/AndroidManifest.xml b/navigation/fragment/src/androidTest/AndroidManifest.xml
index 5e6b71dc9e7..03b4dc38715 100644
--- a/navigation/fragment/src/androidTest/AndroidManifest.xml
+++ b/navigation/fragment/src/androidTest/AndroidManifest.xml
@@ -19,6 +19,7 @@
package="androidx.navigation.fragment">
<uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
<application>
+ <activity android:name="androidx.navigation.fragment.test.NavigationActivity"/>
<activity android:name="androidx.navigation.fragment.XmlNavigationActivity" />
<activity android:name="androidx.navigation.fragment.DynamicNavigationActivity" />
<activity android:name="androidx.navigation.fragment.EmbeddedXmlActivity" />
diff --git a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
index b65f205d6f8..9bad25b147e 100644
--- a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
+++ b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
@@ -23,7 +23,6 @@ import androidx.fragment.app.FragmentFactory
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.navigation.NavOptions
-import androidx.navigation.Navigator
import androidx.navigation.fragment.test.EmptyFragment
import androidx.navigation.fragment.test.R
import androidx.test.annotation.UiThreadTest
@@ -40,9 +39,6 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
@LargeTest
@RunWith(AndroidJUnit4::class)
@@ -179,45 +175,6 @@ class FragmentNavigatorTest {
@UiThreadTest
@Test
- fun testNavigateWithPopUpToThenPopWithFragmentManager() {
- val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
- val backPressListener = mock(Navigator.OnNavigatorBackPressListener::class.java)
- fragmentNavigator.addOnNavigatorBackPressListener(backPressListener)
- val destination = fragmentNavigator.createDestination()
- destination.id = INITIAL_FRAGMENT
- destination.className = EmptyFragment::class.java.name
-
- // Push initial fragment
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
- fragmentManager.executePendingTransactions()
-
- // Push a second fragment
- destination.id = SECOND_FRAGMENT
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
- fragmentManager.executePendingTransactions()
-
- // Pop and then push third fragment, simulating popUpTo to initial.
- val success = fragmentNavigator.popBackStack()
- assertWithMessage("FragmentNavigator should popBackStack successfully")
- .that(success)
- .isTrue()
- destination.id = THIRD_FRAGMENT
- assertThat(fragmentNavigator.navigate(destination, null,
- NavOptions.Builder().setPopUpTo(INITIAL_FRAGMENT, false).build(), null))
- .isEqualTo(destination)
- fragmentManager.executePendingTransactions()
-
- // Now pop the Fragment
- val popped = fragmentManager.popBackStackImmediate()
- assertTrue("FragmentNavigator should return true when popping the third fragment", popped)
- verify(backPressListener).onPopBackStack(fragmentNavigator)
- verifyNoMoreInteractions(backPressListener)
- }
-
- @UiThreadTest
- @Test
fun testSingleTopInitial() {
val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
val destination = fragmentNavigator.createDestination()
@@ -249,8 +206,6 @@ class FragmentNavigatorTest {
@Test
fun testSingleTop() {
val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
- val backPressListener = mock(Navigator.OnNavigatorBackPressListener::class.java)
- fragmentNavigator.addOnNavigatorBackPressListener(backPressListener)
val destination = fragmentNavigator.createDestination()
destination.className = EmptyFragment::class.java.name
@@ -463,47 +418,8 @@ class FragmentNavigatorTest {
@UiThreadTest
@Test
- fun testPopWithFragmentManager() {
- val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
- val backPressListener = mock(Navigator.OnNavigatorBackPressListener::class.java)
- fragmentNavigator.addOnNavigatorBackPressListener(backPressListener)
- val destination = fragmentNavigator.createDestination()
- destination.id = INITIAL_FRAGMENT
- destination.className = EmptyFragment::class.java.name
-
- // First push an initial Fragment
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
- fragmentManager.executePendingTransactions()
- val fragment = fragmentManager.findFragmentById(R.id.container)
- assertNotNull("Fragment should be added", fragment)
-
- // Now push the Fragment that we want to pop
- destination.id = SECOND_FRAGMENT
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
- fragmentManager.executePendingTransactions()
- val replacementFragment = fragmentManager.findFragmentById(R.id.container)
- assertNotNull("Replacement Fragment should be added", replacementFragment)
- assertTrue("Replacement Fragment should be the correct type",
- replacementFragment is EmptyFragment)
- assertEquals("Replacement Fragment should be the primary navigation Fragment",
- replacementFragment, fragmentManager.primaryNavigationFragment)
-
- // Now pop the Fragment
- fragmentManager.popBackStackImmediate()
- verify(backPressListener).onPopBackStack(fragmentNavigator)
- assertEquals("Fragment should be the primary navigation Fragment after pop",
- fragment, fragmentManager.primaryNavigationFragment)
- verifyNoMoreInteractions(backPressListener)
- }
-
- @UiThreadTest
- @Test
- fun testDeepLinkPopWithFragmentManager() {
+ fun testDeepLinkPop() {
val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
- val backPressListener = mock(Navigator.OnNavigatorBackPressListener::class.java)
- fragmentNavigator.addOnNavigatorBackPressListener(backPressListener)
val destination = fragmentNavigator.createDestination()
destination.id = INITIAL_FRAGMENT
destination.className = EmptyFragment::class.java.name
@@ -528,21 +444,18 @@ class FragmentNavigatorTest {
replacementFragment, fragmentManager.primaryNavigationFragment)
// Now pop the Fragment
- fragmentManager.popBackStackImmediate()
- verify(backPressListener).onPopBackStack(fragmentNavigator)
+ fragmentNavigator.popBackStack()
+ fragmentManager.executePendingTransactions()
val fragment = fragmentManager.findFragmentById(R.id.container)
assertEquals("Fragment should be the primary navigation Fragment after pop",
fragment, fragmentManager.primaryNavigationFragment)
- verifyNoMoreInteractions(backPressListener)
}
@UiThreadTest
@Test
- fun testDeepLinkPopWithFragmentManagerWithSaveState() {
+ fun testDeepLinkPopWithSaveState() {
var fragmentNavigator = FragmentNavigator(emptyActivity,
fragmentManager, R.id.container)
- val backPressListener = mock(Navigator.OnNavigatorBackPressListener::class.java)
- fragmentNavigator.addOnNavigatorBackPressListener(backPressListener)
val destination = fragmentNavigator.createDestination()
destination.id = INITIAL_FRAGMENT
destination.className = EmptyFragment::class.java.name
@@ -568,19 +481,16 @@ class FragmentNavigatorTest {
// Create a new FragmentNavigator, replacing the previous one
val savedState = fragmentNavigator.onSaveState()
- fragmentNavigator.removeOnNavigatorBackPressListener(backPressListener)
fragmentNavigator = FragmentNavigator(emptyActivity,
fragmentManager, R.id.container)
fragmentNavigator.onRestoreState(savedState)
- fragmentNavigator.addOnNavigatorBackPressListener(backPressListener)
// Now pop the Fragment
- fragmentManager.popBackStackImmediate()
- verify(backPressListener).onPopBackStack(fragmentNavigator)
+ fragmentNavigator.popBackStack()
+ fragmentManager.executePendingTransactions()
val fragment = fragmentManager.findFragmentById(R.id.container)
assertEquals("Fragment should be the primary navigation Fragment after pop",
fragment, fragmentManager.primaryNavigationFragment)
- verifyNoMoreInteractions(backPressListener)
}
@UiThreadTest
@@ -588,8 +498,6 @@ class FragmentNavigatorTest {
fun testNavigateThenPopAfterSaveState() {
var fragmentNavigator = FragmentNavigator(emptyActivity,
fragmentManager, R.id.container)
- val backPressListener = mock(Navigator.OnNavigatorBackPressListener::class.java)
- fragmentNavigator.addOnNavigatorBackPressListener(backPressListener)
val destination = fragmentNavigator.createDestination()
destination.id = INITIAL_FRAGMENT
destination.className = EmptyFragment::class.java.name
@@ -618,11 +526,9 @@ class FragmentNavigatorTest {
// Create a new FragmentNavigator, replacing the previous one
val savedState = fragmentNavigator.onSaveState()
- fragmentNavigator.removeOnNavigatorBackPressListener(backPressListener)
fragmentNavigator = FragmentNavigator(emptyActivity,
fragmentManager, R.id.container)
fragmentNavigator.onRestoreState(savedState)
- fragmentNavigator.addOnNavigatorBackPressListener(backPressListener)
// Now push a third fragment after the state save
destination.id = THIRD_FRAGMENT
@@ -637,22 +543,18 @@ class FragmentNavigatorTest {
replacementFragment, fragmentManager.primaryNavigationFragment)
// Now pop the Fragment
- fragmentManager.popBackStackImmediate()
- verify(backPressListener).onPopBackStack(fragmentNavigator)
+ fragmentNavigator.popBackStack()
+ fragmentManager.executePendingTransactions()
fragment = fragmentManager.findFragmentById(R.id.container)
assertEquals("Fragment should be the primary navigation Fragment after pop",
fragment, fragmentManager.primaryNavigationFragment)
-
- verifyNoMoreInteractions(backPressListener)
}
@UiThreadTest
@Test
- fun testMultipleNavigateFragmentTransactionsThenPopWithFragmentManager() {
+ fun testMultipleNavigateFragmentTransactionsThenPop() {
val fragmentNavigator = FragmentNavigator(emptyActivity,
fragmentManager, R.id.container)
- val backPressListener = mock(Navigator.OnNavigatorBackPressListener::class.java)
- fragmentNavigator.addOnNavigatorBackPressListener(backPressListener)
val destination = fragmentNavigator.createDestination()
destination.className = EmptyFragment::class.java.name
@@ -665,19 +567,16 @@ class FragmentNavigatorTest {
fragmentNavigator.navigate(destination, null, null, null)
// Now pop the Fragment
- val popped = fragmentManager.popBackStackImmediate()
+ val popped = fragmentNavigator.popBackStack()
+ fragmentManager.executePendingTransactions()
assertTrue("FragmentNavigator should return true when popping the third fragment", popped)
- verify(backPressListener).onPopBackStack(fragmentNavigator)
- verifyNoMoreInteractions(backPressListener)
}
@UiThreadTest
@Test
- fun testMultiplePopFragmentTransactionsThenPopWithFragmentManager() {
+ fun testMultiplePopFragmentTransactionsThenPop() {
val fragmentNavigator = FragmentNavigator(emptyActivity,
fragmentManager, R.id.container)
- val backPressListener = mock(Navigator.OnNavigatorBackPressListener::class.java)
- fragmentNavigator.addOnNavigatorBackPressListener(backPressListener)
val destination = fragmentNavigator.createDestination()
destination.className = EmptyFragment::class.java.name
@@ -696,10 +595,9 @@ class FragmentNavigatorTest {
fragmentNavigator.popBackStack()
fragmentNavigator.popBackStack()
- val popped = fragmentManager.popBackStackImmediate()
+ val popped = fragmentNavigator.popBackStack()
+ fragmentManager.executePendingTransactions()
assertTrue("FragmentNavigator should return true when popping the third fragment", popped)
- verify(backPressListener).onPopBackStack(fragmentNavigator)
- verifyNoMoreInteractions(backPressListener)
}
}
diff --git a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/OnBackPressedTest.kt b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/OnBackPressedTest.kt
new file mode 100644
index 00000000000..e3dc4c6e39a
--- /dev/null
+++ b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/OnBackPressedTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.fragment
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.test.EmptyFragment
+import androidx.navigation.fragment.test.NavigationActivity
+import androidx.navigation.fragment.test.R
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class OnBackPressedTest {
+
+ @get:Rule
+ var activityRule = ActivityTestRule(NavigationActivity::class.java)
+
+ @UiThreadTest
+ @Test
+ fun testOnBackPressedOnRoot() {
+ val activity = activityRule.activity
+ val navController = activity.navController
+ navController.setGraph(R.navigation.nav_simple)
+ activity.onBackPressed()
+ assertWithMessage("onBackPressed() should finish the activity on the root")
+ .that(activity.isFinishing)
+ .isTrue()
+ }
+
+ @UiThreadTest
+ @Test
+ fun testOnBackPressedAfterNavigate() {
+ val activity = activityRule.activity
+ val navController = activity.navController
+ navController.setGraph(R.navigation.nav_simple)
+ navController.navigate(R.id.empty_fragment)
+
+ activity.onBackPressed()
+ assertWithMessage("onBackPressed() should trigger NavController.popBackStack()")
+ .that(navController.currentDestination?.id)
+ .isEqualTo(R.id.start_fragment)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testOnBackPressedWithChildBackStack() {
+ val activity = activityRule.activity
+ val navHostFragment = activity.supportFragmentManager.primaryNavigationFragment
+ as NavHostFragment
+ val navHostFragmentManager = navHostFragment.childFragmentManager
+ val navController = navHostFragment.navController
+ navController.setGraph(R.navigation.nav_simple)
+ navController.navigate(R.id.child_back_stack_fragment)
+ navHostFragmentManager.executePendingTransactions()
+
+ val currentFragment = navHostFragmentManager.primaryNavigationFragment
+ as ChildBackStackFragment
+ assertWithMessage("Current Fragment should have a child Fragment by default")
+ .that(currentFragment.childFragment)
+ .isNotNull()
+
+ activity.onBackPressed()
+ assertWithMessage("onBackPressed() should not trigger NavController when there is a " +
+ "child back stack")
+ .that(navController.currentDestination?.id)
+ .isEqualTo(R.id.child_back_stack_fragment)
+ assertWithMessage("Child Fragment should be popped")
+ .that(currentFragment.childFragment)
+ .isNull()
+ }
+}
+
+class ChildBackStackFragment : EmptyFragment() {
+ val childFragment get() = childFragmentManager.findFragmentByTag("child")
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ childFragmentManager.beginTransaction()
+ .add(Fragment(), "child")
+ .addToBackStack(null)
+ .commit()
+ }
+} \ No newline at end of file
diff --git a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/EmptyFragment.kt b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/EmptyFragment.kt
index 8ab7d3f97fe..094edf20872 100644
--- a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/EmptyFragment.kt
+++ b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/EmptyFragment.kt
@@ -23,7 +23,7 @@ import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.fragment.app.Fragment
-class EmptyFragment : Fragment() {
+open class EmptyFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
diff --git a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/NavigationActivity.kt b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/NavigationActivity.kt
new file mode 100644
index 00000000000..1669f10c585
--- /dev/null
+++ b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/NavigationActivity.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.fragment.test
+
+import androidx.fragment.app.FragmentActivity
+import androidx.navigation.findNavController
+
+class NavigationActivity : FragmentActivity(R.layout.navigation_activity) {
+ val navController get() = findNavController(R.id.nav_host)
+} \ No newline at end of file
diff --git a/navigation/fragment/src/androidTest/res/navigation/nav_simple.xml b/navigation/fragment/src/androidTest/res/navigation/nav_simple.xml
new file mode 100644
index 00000000000..95f2c5790e3
--- /dev/null
+++ b/navigation/fragment/src/androidTest/res/navigation/nav_simple.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/nav_simple"
+ app:startDestination="@+id/start_fragment">
+ <fragment
+ android:id="@+id/start_fragment"
+ android:name="androidx.navigation.fragment.test.EmptyFragment"/>
+ <fragment
+ android:id="@+id/empty_fragment"
+ android:name="androidx.navigation.fragment.test.EmptyFragment"/>
+ <fragment
+ android:id="@+id/child_back_stack_fragment"
+ android:name="androidx.navigation.fragment.ChildBackStackFragment"/>
+</navigation> \ No newline at end of file
diff --git a/navigation/fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.java b/navigation/fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.java
index 9c125546562..da02a88e23e 100644
--- a/navigation/fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.java
+++ b/navigation/fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.java
@@ -39,7 +39,6 @@ import androidx.navigation.NavigatorProvider;
import java.util.ArrayDeque;
import java.util.Collections;
-import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -62,54 +61,9 @@ public class FragmentNavigator extends Navigator<FragmentNavigator.Destination>
private static final String KEY_BACK_STACK_IDS = "androidx-nav-fragment:navigator:backStackIds";
private final Context mContext;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final FragmentManager mFragmentManager;
+ private final FragmentManager mFragmentManager;
private final int mContainerId;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- ArrayDeque<Integer> mBackStack = new ArrayDeque<>();
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- boolean mIsPendingBackStackOperation = false;
-
- /**
- * A fragment manager back stack change listener used to detect a fragment being popped due to
- * onBackPressed().
- *
- * Since a back press is a pop in the FragmentManager not caused by this navigator a flag is
- * used to identify operations by this navigator. If the flag is ON, this listener doesn't do
- * anything since the change in the fragment manager's back stack was caused by the navigator.
- * The flag is reset once this navigator's back stack matches the fragment manager's back stack.
- * If the flag is OFF then a change in the back stack was not caused by this navigator, it is
- * then appropriate to check if a fragment was popped to dispatch navigator events.
- *
- * Note that onBackPressed() invokes popBackStackImmediate(), meaning pending transactions - if
- * any - before the pop will be executed causing this listener to be called one or more times
- * until the flag is reset. Finally, the pop due to pressing back occurs, at which it is
- * appropriate to dispatch a navigator popped event.
- */
- private final FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener =
- new FragmentManager.OnBackStackChangedListener() {
-
- @Override
- public void onBackStackChanged() {
- // If we have pending operations made by us then consume this change, otherwise
- // detect a pop in the back stack to dispatch callback.
- if (mIsPendingBackStackOperation) {
- mIsPendingBackStackOperation = !isBackStackEqual();
- return;
- }
-
- // The initial Fragment won't be on the back stack, so the
- // real count of destinations is the back stack entry count + 1
- int newCount = mFragmentManager.getBackStackEntryCount() + 1;
- if (newCount < mBackStack.size()) {
- // Handle cases where the user hit the system back button
- while (mBackStack.size() > newCount) {
- mBackStack.removeLast();
- }
- dispatchOnNavigatorBackPress();
- }
- }
- };
+ private ArrayDeque<Integer> mBackStack = new ArrayDeque<>();
public FragmentNavigator(@NonNull Context context, @NonNull FragmentManager manager,
int containerId) {
@@ -118,16 +72,6 @@ public class FragmentNavigator extends Navigator<FragmentNavigator.Destination>
mContainerId = containerId;
}
- @Override
- protected void onBackPressAdded() {
- mFragmentManager.addOnBackStackChangedListener(mOnBackStackChangedListener);
- }
-
- @Override
- protected void onBackPressRemoved() {
- mFragmentManager.removeOnBackStackChangedListener(mOnBackStackChangedListener);
- }
-
/**
* {@inheritDoc}
* <p>
@@ -154,7 +98,6 @@ public class FragmentNavigator extends Navigator<FragmentNavigator.Destination>
mFragmentManager.popBackStack(
generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
- mIsPendingBackStackOperation = true;
} // else, we're on the first Fragment, so there's nothing to pop from FragmentManager
mBackStack.removeLast();
return true;
@@ -204,6 +147,7 @@ public class FragmentNavigator extends Navigator<FragmentNavigator.Destination>
* asynchronously, so the new Fragment is not instantly available
* after this call completes.
*/
+ @SuppressWarnings("deprecation") /* Using instantiateFragment for forward compatibility */
@Nullable
@Override
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
@@ -217,7 +161,6 @@ public class FragmentNavigator extends Navigator<FragmentNavigator.Destination>
if (className.charAt(0) == '.') {
className = mContext.getPackageName() + className;
}
- //noinspection deprecation needed to maintain forward compatibility
final Fragment frag = instantiateFragment(mContext, mFragmentManager,
className, args);
frag.setArguments(args);
@@ -259,12 +202,10 @@ public class FragmentNavigator extends Navigator<FragmentNavigator.Destination>
generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
- mIsPendingBackStackOperation = true;
}
isAdded = false;
} else {
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
- mIsPendingBackStackOperation = true;
isAdded = true;
}
if (navigatorExtras instanceof Extras) {
@@ -336,40 +277,6 @@ public class FragmentNavigator extends Navigator<FragmentNavigator.Destination>
}
/**
- * Checks if this FragmentNavigator's back stack is equal to the FragmentManager's back stack.
- */
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- boolean isBackStackEqual() {
- int fragmentBackStackCount = mFragmentManager.getBackStackEntryCount();
- // Initial fragment won't be on the FragmentManager's back stack so +1 its count.
- if (mBackStack.size() != fragmentBackStackCount + 1) {
- return false;
- }
-
- // From top to bottom verify destination ids match in both back stacks/
- Iterator<Integer> backStackIterator = mBackStack.descendingIterator();
- int fragmentBackStackIndex = fragmentBackStackCount - 1;
- while (backStackIterator.hasNext() && fragmentBackStackIndex >= 0) {
- int destId = backStackIterator.next();
- try {
- int fragmentDestId = getDestId(mFragmentManager
- .getBackStackEntryAt(fragmentBackStackIndex--)
- .getName());
- if (destId != fragmentDestId) {
- return false;
- }
- } catch (NumberFormatException e) {
- throw new IllegalStateException("Invalid back stack entry on the "
- + "NavHostFragment's back stack - use getChildFragmentManager() "
- + "if you need to do custom FragmentTransactions from within "
- + "Fragments created via your navigation graph.");
- }
- }
-
- return true;
- }
-
- /**
* NavDestination specific to {@link FragmentNavigator}
*/
@NavDestination.ClassType(Fragment.class)
diff --git a/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java b/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
index c289b8a9169..8a344e96a18 100644
--- a/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
+++ b/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
@@ -202,8 +202,12 @@ public class NavHostFragment extends Fragment implements NavHost {
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Context context = requireContext();
+ // TODO Fix Fragments to register their OnBackPressedCallback eagerly
+ getChildFragmentManager();
mNavController = new NavController(context);
+ mNavController.setHostLifecycleOwner(this);
+ mNavController.setHostOnBackPressedDispatcherOwner(requireActivity());
mNavController.setHostViewModelStore(getViewModelStore());
onCreateNavController(mNavController);
diff --git a/navigation/runtime/api/2.1.0-alpha03.txt b/navigation/runtime/api/2.1.0-alpha03.txt
index 2fd3e0f1c35..8fe3dc47333 100644
--- a/navigation/runtime/api/2.1.0-alpha03.txt
+++ b/navigation/runtime/api/2.1.0-alpha03.txt
@@ -68,6 +68,8 @@ package androidx.navigation {
method @CallSuper public void setGraph(@NavigationRes int, android.os.Bundle?);
method @CallSuper public void setGraph(androidx.navigation.NavGraph);
method @CallSuper public void setGraph(androidx.navigation.NavGraph, android.os.Bundle?);
+ method public void setHostLifecycleOwner(androidx.lifecycle.LifecycleOwner);
+ method public void setHostOnBackPressedDispatcherOwner(androidx.activity.OnBackPressedDispatcherOwner);
method public void setHostViewModelStore(androidx.lifecycle.ViewModelStore);
field public static final String KEY_DEEP_LINK_INTENT = "android-support-nav:controller:deepLinkIntent";
}
diff --git a/navigation/runtime/api/current.txt b/navigation/runtime/api/current.txt
index 2fd3e0f1c35..8fe3dc47333 100644
--- a/navigation/runtime/api/current.txt
+++ b/navigation/runtime/api/current.txt
@@ -68,6 +68,8 @@ package androidx.navigation {
method @CallSuper public void setGraph(@NavigationRes int, android.os.Bundle?);
method @CallSuper public void setGraph(androidx.navigation.NavGraph);
method @CallSuper public void setGraph(androidx.navigation.NavGraph, android.os.Bundle?);
+ method public void setHostLifecycleOwner(androidx.lifecycle.LifecycleOwner);
+ method public void setHostOnBackPressedDispatcherOwner(androidx.activity.OnBackPressedDispatcherOwner);
method public void setHostViewModelStore(androidx.lifecycle.ViewModelStore);
field public static final String KEY_DEEP_LINK_INTENT = "android-support-nav:controller:deepLinkIntent";
}
diff --git a/navigation/runtime/build.gradle b/navigation/runtime/build.gradle
index 3a02683f2a4..d21d9e69e7c 100644
--- a/navigation/runtime/build.gradle
+++ b/navigation/runtime/build.gradle
@@ -33,6 +33,7 @@ android {
dependencies {
api(project(":navigation:navigation-common"))
+ api(project(":activity"))
api(project(":lifecycle:lifecycle-viewmodel"))
androidTestImplementation(project(":navigation:navigation-testing"))
diff --git a/navigation/runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt b/navigation/runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
index bfbf4ef9b66..bab44e2e330 100644
--- a/navigation/runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
+++ b/navigation/runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -505,52 +505,6 @@ class NavControllerTest {
}
@Test
- fun testNavigateFromNestedThenNavigatorInstigatedPop() {
- val navController = createNavController()
- navController.setGraph(R.navigation.nav_nested_start_destination)
- val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
- assertEquals(R.id.nested_test, navController.currentDestination?.id ?: 0)
- assertEquals(1, navigator.backStack.size)
-
- navController.navigate(R.id.second_test)
- assertEquals(R.id.second_test, navController.currentDestination?.id ?: 0)
- assertEquals(2, navigator.backStack.size)
-
- // A Navigator can pop a destination off its own back stack
- // then inform the NavController via dispatchOnNavigatorNavigated
- navigator.backStack.removeLast()
- val newDestination = navigator.current.first
- assertNotNull(newDestination)
- navigator.dispatchOnNavigatorBackPress()
- assertEquals(R.id.nested_test, navController.currentDestination?.id ?: 0)
- assertEquals(1, navigator.backStack.size)
- }
-
- @Test
- fun testNavigateNestedPopUpToThenNavigatorInstigatedPop() {
- val navController = createNavController()
- navController.setGraph(R.navigation.nav_nested_start_destination)
- val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
- assertEquals(R.id.nested_test, navController.currentDestination?.id ?: 0)
- assertEquals(1, navigator.backStack.size)
-
- navController.navigate(R.id.pop_forward)
- assertEquals(R.id.nested_second_test, navController.currentDestination?.id ?: 0)
- assertEquals(1, navigator.backStack.size)
-
- // A Navigator can pop a destination off its own back stack
- // then inform the NavController via dispatchOnNavigatorNavigated
- navigator.backStack.removeLast()
- navigator.dispatchOnNavigatorBackPress()
- assertWithMessage("The last destination should be popped off the stack")
- .that(navController.currentDestination)
- .isNull()
- assertWithMessage("TestNavigator should not nothing on its back stack")
- .that(navigator.backStack.size)
- .isEqualTo(0)
- }
-
- @Test
fun testNavigateThenNavigateWithPop() {
val navController = createNavController()
navController.setGraph(R.navigation.nav_simple)
diff --git a/navigation/runtime/src/main/java/androidx/navigation/NavController.java b/navigation/runtime/src/main/java/androidx/navigation/NavController.java
index 976374e4177..b5e664c7074 100644
--- a/navigation/runtime/src/main/java/androidx/navigation/NavController.java
+++ b/navigation/runtime/src/main/java/androidx/navigation/NavController.java
@@ -25,12 +25,16 @@ import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
+import androidx.activity.OnBackPressedCallback;
+import androidx.activity.OnBackPressedDispatcher;
+import androidx.activity.OnBackPressedDispatcherOwner;
import androidx.annotation.CallSuper;
import androidx.annotation.IdRes;
import androidx.annotation.NavigationRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.TaskStackBuilder;
+import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelStore;
import java.util.ArrayDeque;
@@ -87,57 +91,10 @@ public class NavController {
@SuppressWarnings("WeakerAccess") /* synthetic access */
final Deque<NavBackStackEntry> mBackStack = new ArrayDeque<>();
+ private LifecycleOwner mLifecycleOwner;
private NavControllerViewModel mViewModel;
- private final NavigatorProvider mNavigatorProvider = new NavigatorProvider() {
- @Nullable
- @Override
- public Navigator<? extends NavDestination> addNavigator(@NonNull String name,
- @NonNull Navigator<? extends NavDestination> navigator) {
- Navigator<? extends NavDestination> previousNavigator =
- super.addNavigator(name, navigator);
- if (previousNavigator != navigator) {
- if (previousNavigator != null) {
- previousNavigator.removeOnNavigatorBackPressListener(mOnBackPressListener);
- }
- navigator.addOnNavigatorBackPressListener(mOnBackPressListener);
- }
- return previousNavigator;
- }
- };
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Navigator.OnNavigatorBackPressListener mOnBackPressListener =
- new Navigator.OnNavigatorBackPressListener() {
- @Override
- public void onPopBackStack(@NonNull Navigator navigator) {
- // Find what destination just got popped
- NavDestination lastFromNavigator = null;
- Iterator<NavBackStackEntry> iterator = mBackStack.descendingIterator();
- while (iterator.hasNext()) {
- NavDestination destination = iterator.next().getDestination();
- Navigator currentNavigator = getNavigatorProvider().getNavigator(
- destination.getNavigatorName());
- if (currentNavigator == navigator) {
- lastFromNavigator = destination;
- break;
- }
- }
- if (lastFromNavigator == null) {
- throw new IllegalArgumentException("Navigator " + navigator
- + " reported pop but did not have any destinations"
- + " on the NavController back stack");
- }
- // Pop all intervening destinations from other Navigators off the
- // back stack
- popBackStackInternal(lastFromNavigator.getId(), false);
- // Now record the pop operation that we were sent
- if (!mBackStack.isEmpty()) {
- mBackStack.removeLast();
- }
- dispatchOnDestinationChanged();
- }
- };
+ private final NavigatorProvider mNavigatorProvider = new NavigatorProvider();
private final CopyOnWriteArrayList<OnDestinationChangedListener>
mOnDestinationChangedListeners = new CopyOnWriteArrayList<>();
@@ -1032,6 +989,52 @@ public class NavController {
}
/**
+ * Sets the host's {@link LifecycleOwner}.
+ *
+ * @param owner The {@link LifecycleOwner} associated with the containing {@link NavHost}.
+ * @see #setHostOnBackPressedDispatcherOwner(OnBackPressedDispatcherOwner)
+ */
+ public void setHostLifecycleOwner(@NonNull LifecycleOwner owner) {
+ mLifecycleOwner = owner;
+ }
+
+ /**
+ * Sets the host's {@link OnBackPressedDispatcherOwner}. If set, NavController will
+ * register a {@link OnBackPressedCallback} to handle system Back button events.
+ * <p>
+ * If you have not explicitly called {@link #setHostLifecycleOwner(LifecycleOwner)},
+ * the owner you pass here will be used as the {@link LifecycleOwner} for registering
+ * the {@link OnBackPressedCallback}.
+ *
+ * @param owner The {@link OnBackPressedDispatcherOwner} associated with the containing
+ * {@link NavHost}.
+ * @see #setHostLifecycleOwner(LifecycleOwner)
+ */
+ public void setHostOnBackPressedDispatcherOwner(@NonNull OnBackPressedDispatcherOwner owner) {
+ if (mLifecycleOwner == null) {
+ mLifecycleOwner = owner;
+ }
+ OnBackPressedDispatcher dispatcher = owner.getOnBackPressedDispatcher();
+ dispatcher.addCallback(mLifecycleOwner, new OnBackPressedCallback(true) {
+ @Override
+ public boolean isEnabled() {
+ int destinationCount = 0;
+ for (NavBackStackEntry entry : mBackStack) {
+ if (!(entry.getDestination() instanceof NavGraph)) {
+ destinationCount++;
+ }
+ }
+ return destinationCount > 1;
+ }
+
+ @Override
+ public void handleOnBackPressed() {
+ popBackStack();
+ }
+ });
+ }
+
+ /**
* Sets the host's ViewModelStore used by the NavController to store ViewModels at the
* navigation graph level. This is required to call {@link #getViewModelStore} and
* should generally be called for you by your {@link NavHost}.
diff --git a/navigation/runtime/src/main/java/androidx/navigation/NavHost.java b/navigation/runtime/src/main/java/androidx/navigation/NavHost.java
index a6b59d5ed9c..c8bdc194c32 100644
--- a/navigation/runtime/src/main/java/androidx/navigation/NavHost.java
+++ b/navigation/runtime/src/main/java/androidx/navigation/NavHost.java
@@ -19,7 +19,10 @@ package androidx.navigation;
import android.os.Bundle;
import android.view.View;
+import androidx.activity.OnBackPressedDispatcherOwner;
import androidx.annotation.NonNull;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.ViewModelStore;
/**
* A host is a single context or container for navigation via a {@link NavController}.
@@ -29,6 +32,17 @@ import androidx.annotation.NonNull;
* <li>Handle {@link NavController#saveState() saving} and
* {@link NavController#restoreState(Bundle) restoring} their controller's state</li>
* <li>Call {@link Navigation#setViewNavController(View, NavController)} on their root view</li>
+ * <li>Route system Back button events to the NavController either by manually calling
+ * {@link NavController#popBackStack()} or by calling
+ * {@link NavController#setHostOnBackPressedDispatcherOwner(OnBackPressedDispatcherOwner)}
+ * when constructing the NavController.</li>
+ * </ul>
+ * Optionally, a navigation host should consider calling:
+ * <ul>
+ * <li>Call {@link NavController#setHostLifecycleOwner(LifecycleOwner)} to associate the
+ * NavController with a specific Lifecycle.</li>
+ * <li>Call {@link NavController#setHostViewModelStore(ViewModelStore)} to enable usage of
+ * {@link NavController#getViewModelStore(int)} and navigation graph scoped ViewModels.</li>
* </ul>
*/
public interface NavHost {