When I have multiple top level destinations in my navigation graph, and when I click back-key to finish my app, those destinations other than the startDestination
won't finish but pop the startDestination
Fragment.
I've tried app:popUpTo="@id/nav_graph"
and/or app:popUpToInclusive="true"
but in vain.
Activity:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
AppBarConfiguration appBarConfiguration =
new AppBarConfiguration.Builder(R.id.fragmentA, R.id.fragmentB, R.id.fragmentC)
.setOpenableLayout(drawerLayout)
.build();
NavigationView navView = findViewById(R.id.nav_view);
NavigationUI.setupWithNavController(navView, navController);
Toolbar toolbar = findViewById(R.id.toolbar);
NavigationUI.setupWithNavController(
toolbar,
navController,
appBarConfiguration
);
}
}
layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<!-- Use DrawerLayout as root container for activity -->
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- Layout to contain contents of navigation_view body of screen (drawer will slide over this) -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="@style/AppTheme.AppBarOverlay"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/AppTheme.PopupOverlay" />
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- Container for contents of drawer - use NavigationView to make configuration easier -->
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/navigation_header"
app:menu="@menu/navigation_view" />
</androidx.drawerlayout.widget.DrawerLayout>
nav_graph:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/fragmentA">
<fragment
android:id="@+id/fragmentA"
android:name="com.stackoverflow.FragmentA"
android:label="@string/fragmentA">
<argument
android:name="message"
android:defaultValue="(no message)"
app:argType="string" />
</fragment>
<action
android:id="@+id/toFragmentAWithMessage1"
app:destination="@id/fragmentA">
<argument
android:name="message"
android:defaultValue="message 1"
app:argType="string" />
</action>
<action
android:id="@+id/toFragmentAWithMessage2"
app:destination="@id/fragmentA">
<argument
android:name="message"
android:defaultValue="message 2"
app:argType="string" />
</action>
<fragment
android:id="@+id/fragmentB"
android:name="com.stackoverflow.navigationui.FragmentB"
android:label="@string/fragmentB">
<argument
android:name="message"
android:defaultValue="(no message)"
app:argType="string" />
</fragment>
<action
android:id="@+id/toFragmentBWithMessage1"
app:destination="@id/fragmentB">
<argument
android:name="message"
android:defaultValue="message 1"
app:argType="string" />
</action>
<action
android:id="@+id/toFragmentBWithMessage2"
app:destination="@id/fragmentB">
<argument
android:name="message"
android:defaultValue="message 2"
app:argType="string" />
</action>
<fragment
android:id="@+id/fragmentC"
android:name="com.stackoverflow.FragmentC"
android:label="@string/fragmentC">
<argument
android:name="message"
android:defaultValue="(no message)"
app:argType="string" />
</fragment>
<action
android:id="@+id/toFragmentCWithMessage1"
app:destination="@id/fragmentC">
<argument
android:name="message"
android:defaultValue="message 1"
app:argType="string" />
</action>
<action
android:id="@+id/toFragmentCWithMessage2"
app:destination="@id/fragmentC">
<argument
android:name="message"
android:defaultValue="message 2"
app:argType="string" />
</action>
</navigation>
I know overriding OnBackPressedCallback
is a workaround.
public class FragmentB extends Fragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requireActivity().getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
remove();
requireActivity().finish();
}
});
}
}
Is there a more fundamental solution?
Just remove or set false to the app:defaultNavHost
attribute of the NavHostFragment's <fragment>
in the layout.xml:
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="false"
/>
Interact programmatically with the Navigation component
Note that
setPrimaryNavigationFragment(finalHost)
lets your NavHost intercept system Back button presses. You can also implement this behavior in your NavHost XML by addingapp:defaultNavHost="true"
.
Set a currently active fragment in this FragmentManager as the primary navigation fragment.
The primary navigation fragment's child FragmentManager will be called first to process delegated navigation actions such as
FragmentManager.popBackStack()
if no ID or transaction name is provided to pop to. Navigation operations outside of the fragment system may choose to delegate those actions to the primary navigation fragment as returned byFragmentManager.getPrimaryNavigationFragment()
.
Because of your setting true to app:defaultNavHost
, your NavHost intercepts back-key presses and leads to pop the startDestination FragmentA.