Android SharedPreferences Tutorial and Example

Handling back navigation with the Navigation Component

Back navigation is a core part of Android application development, but it becomes more complex when an app contains several fragments, nested flows, dialogs, and bottom navigation tabs. The Navigation Component provides a consistent way to manage these destinations through a navigation graph and a NavController.

For Australian users moving between screens on a phone during a commute in Sydney, Melbourne, or Brisbane, navigation should feel immediate and predictable. A back press should return to the previous meaningful screen rather than unexpectedly closing the app or resetting a form.

This guide explains how the back stack works, how to connect it to an activity, and how to manage special cases such as authentication flows, dialog destinations, and selected bottom-navigation tabs. The examples use Kotlin and AndroidX libraries.

The same principles apply whether you are building a small SQLite project, a shopping app for the Australian market, or a larger application that must follow the Australian Privacy Principles when handling personal information.

Understand the navigation back stack

The Navigation Component stores destinations in a back stack. When code navigates from HomeFragment to DetailsFragment, the home destination remains below the details destination. Pressing the system Back button removes the current entry and displays the previous one.

A basic navigation graph might look like this:

<navigation
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_nav"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.example.app.HomeFragment" />

    <fragment
        android:id="@+id/detailsFragment"
        android:name="com.example.app.DetailsFragment" />
</navigation>

The startDestination is the first screen shown. It normally should not remain as a duplicate entry after returning from a child screen. The navigation graph defines possible routes, while the NavController manages the active route and its history.

Connect the NavController to your activity

Place a NavHostFragment in the activity layout. It hosts the fragments declared in the navigation graph:

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/nav_host"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:defaultNavHost="true"
    app:navGraph="@navigation/main_nav" />

The app:defaultNavHost="true" attribute allows the host to receive system Back events. In an activity, you can also obtain the controller explicitly:

val navController = findNavController(R.id.nav_host)

With a standard single-activity architecture, most back handling belongs to the navigation host rather than individual fragments. This reduces duplicated code and avoids fragments competing to consume the same event.

Navigate forward and return safely

Use an action or destination ID to move forward:

findNavController().navigate(
    HomeFragmentDirections
        .actionHomeFragmentToDetailsFragment(itemId)
)

The generated Safe Args class passes itemId with the correct type. On the details screen, pressing Back generally needs no custom code:

requireActivity().onBackPressedDispatcher
    .onBackPressed()

In newer projects, prefer the dispatcher with a lifecycle-aware callback when a fragment has special behaviour. For example, a form may need to warn about unsaved changes before leaving. The callback should be enabled only while the fragment is visible and should delegate to the controller when no special action is required.

Avoid calling popBackStack() repeatedly in response to one press. One user action should remove one logical destination unless the design clearly requires returning to a known root.

Choose the right back-stack operation

Different navigation methods produce different histories. navigate() adds a destination, while popBackStack() removes the current destination. popBackStack(destinationId, inclusive) returns to a specific destination and can optionally remove that destination too.

Operation Typical use Effect on history
navigate() Open details or a child flow Adds a destination
popBackStack() Return to the previous screen Removes the current destination
popBackStack(id, false) Return to an existing parent Keeps the target destination
navigateUp() Toolbar Up navigation Moves towards the app hierarchy
popUpTo(id) { inclusive = true } Finish login or checkout Clears destinations up to a point

For a login flow, successful authentication should usually remove the login screens so Back does not reveal them again:

findNavController().navigate(
    R.id.homeFragment,
    null,
    navOptions {
        popUpTo(R.id.loginFragment) {
            inclusive = true
        }
    }
)

For a checkout process, clearing completed steps can also prevent users from reopening outdated payment screens. This is particularly important for Australian services that may use third-party payment providers and must handle sensitive customer information carefully.

Handle system Back and toolbar Up

System Back and toolbar Up are related but not identical. Back usually returns through the user's visited history. Up moves towards the parent in the app's navigation hierarchy. The Navigation Component can connect the toolbar to the controller:

val navController = findNavController(R.id.nav_host)
val appBarConfiguration = AppBarConfiguration(navController.graph)

setupActionBarWithNavController(navController, appBarConfiguration)

Then delegate Up presses from the activity:

override fun onSupportNavigateUp(): Boolean {
    return findNavController(R.id.nav_host).navigateUp() ||
        super.onSupportNavigateUp()
}

For bottom navigation, use NavigationUI.setupWithNavController(). Each top-level tab can preserve its own state, which is useful for apps where people switch between sections while travelling on Melbourne trains or checking information during a short break.

Preserve state without corrupting history

Back navigation should not be used as a substitute for data persistence. Temporary UI state belongs in a ViewModel or SavedStateHandle, while small preferences such as display settings can use SharedPreferences. A practical reference for storing and retrieving those values is this SharedPreferences guide.

For example, a selected filter can be restored after configuration changes without adding another destination to the back stack. A completed onboarding flag may be persisted, but the onboarding destinations should still be removed after completion with popUpTo.

For sensitive data, avoid storing passwords or payment credentials in plain preferences. Australian Privacy Principles require organisations to take reasonable steps to protect personal information, so authentication tokens should use an appropriate secure storage strategy and should be removed when the user signs out.

Test edge cases on real devices

Back behaviour can differ between an emulator and a physical phone. Test gesture navigation, three-button navigation, screen rotation, process recreation, and a device with a smaller display. Also test Android versions commonly supported by your application rather than relying only on the latest emulator image.

Useful checks include:

  • Back from a details screen returns to the correct list position.
  • Back from a dialog closes the dialog before its parent screen.
  • Repeated taps do not create duplicate destinations.
  • Sign-out clears private screens from the history.

A local Australian test matrix should include common Android handsets used in the Google Play market, different screen densities, and intermittent mobile connectivity. A user on a regional Queensland connection should receive the same safe back behaviour as someone using fast Wi-Fi in Perth.

For small practice projects, examples from Android project tutorials can help you compare fragment-based navigation with simpler activity-based flows. Adapt any example to current AndroidX versions before using it in production.

Avoid common navigation mistakes

A frequent error is creating a new activity for every screen while also using fragments inside each activity. This can make Back behaviour difficult to predict. A single activity with a clear navigation graph is often easier to maintain, although multiple activities remain valid for separate tasks or external entry points.

Another issue is using launchSingleTop without understanding its purpose. It prevents a new copy of the current destination when navigating to the same route, but it does not automatically restore every previous state. Combine it with restoreState and saveState when implementing bottom-navigation behaviour.

Keep navigation decisions close to the feature that owns them, pass only the data a destination needs, and use lifecycle-aware callbacks for custom Back handling. With a carefully designed graph, the system Back button, toolbar Up button, and gesture navigation can provide one consistent experience across Android phones.