Implementing Up Navigation on Android

August 03, 2018 | Romain Guefveneu | 5-minute read

Parent Navigation has always been a tough topic on Android. There are not a lot of apps that implement the guidelines correctly, maybe because they are hard to understand or complicated to implement. Even the Google apps don’t implement them: it’s always frustrating to take a screenshot, press Up on the preview and not be redirected to the Google Photo app 😞.

Unlike the Back button, which should come back to the previous screen – even if that screen was not from the same app –, the Up button should stay in the same app.

Let’s see how to implement this navigation.

A Simple App

Here is a simple music app, with 3 activities:

  • a Main activity, with a list of albums
  • an Album activity, with a list of tracks
  • a Track activity, with the track’s name and that of the album.

Forward navigation is pretty obvious:

Forward Navigation

On MainActivity, users can go to AlbumActivity or TrackActivity.
Nothing special here. So what about back navigation?

Back Navigation

Since we’re in the same task, Up navigation and Back navigation do the same thing: they come back to the previous activity. But if we don’t start from the main activity (for instance from a notification or a widget), Up and Back won’t have the same behavior:

  • Back will still dismiss the current activity (or fragment), so users will come back to the previous app
  • Up should redirect to the app’s parent activity.

Notification Navigation

How to implement

No need to write anything in the Activity#onOptionsItemSelected method! Everything is already done in the Android SDK. We mainly just need to edit the AndroidManifest.xml file to add some attributes to our activities.

Declare a parentActivityName

First, we need to declare a parent activity for each child activity: TrackActivity will come back to AlbumActivity, which itself comes back to MainActivity.

<activity
    android:name=".AlbumActivity"
    android:parentActivityName=".MainActivity"
    ... />

<activity
    android:name=".TrackActivity"
    android:parentActivityName=".AlbumActivity"
    ... />

See also : getParentActivityIntent

But that’s not enough: now when pressing the Up button a new activity is created – even if we’re in the same task –, instead of dimissing the current activity.

The parent activity is recreated when pressing up. Subtle, isn’t it?

Declare a launchMode

By declaring parent activities’ launchMode as singleTop, we prevent the system from creating a new activity each time we press Up.

<activity
    android:name=".MainActivity"
    android:launchMode="singleTop"
    ...>
    ...
</activity>
<activity
    android:name=".AlbumActivity"
    android:launchMode="singleTop"
    ... />
The parent activity is no longer recreated. 🎉

Now we have the desired behavior, but we don’t cover all the cases. What if I start TrackActivity from outside the app, and press Up? I want to be redirected to AlbumActivity.

TrackActivity is not redirect to AlbumActivity when pressing Up. Mildly frustrating, to say the least.

To do that, we have to declare a taskAffinity.

Declare a taskAffinity

Declaring a taskAffinity for TrackActivity allows a new task to be created when starting this activity from outside the app. Thanks to that, Up navigation will switch to the main task and create an AlbumActivity.
Curious about how it works? See Activity#onNavigateUp

<activity
    android:name=".TrackActivity"
    android:taskAffinity=".Track" 
    .../>

One issue here: AlbumActivity needs to know which album we want to display.

Override onPrepareSupportNavigateUpTaskStack

On TrackActivity, we need to override onPrepareSupportNavigateUpTaskStack to edit the intent that will start the parent activity when pressing Up:

override fun onPrepareSupportNavigateUpTaskStack(builder: TaskStackBuilder) {
    super.onPrepareSupportNavigateUpTaskStack(builder)
    val albumId = intent.getLongExtra(TrackActivity.EXTRA_ALBUM_ID, -1L)
    val albumIntent = AlbumActivity.create(this, albumId)
    builder.editIntentAt(builder.intentCount - 1)?.putExtras(albumIntent)
}

See also onCreateNavigateUpTaskStack.

TrackActivity is now redirected to AlbumActivity when pressing Up. 👌

One last thing: because we create a new task when starting TrackActivity from outside the app, TrackActivity will remain on the Recents screen when pressing Up. It would be great to remove it automatically.

TrackActivity is still visible in the Recents screen. Not very useful.

Declare autoRemoveFromRecents

With autoRemoveFromRecents, the activity will be removed from the Recents screen when its task is completed, for instance when coming back to the parent activity.

<activity
    android:name=".TrackActivity"
    android:autoRemoveFromRecents="true"
    .../>
TrackActivity is no more visible in the Recents screen. 🙌

Summary

To sum up, this is what the AndroidManifest.xml looks like:

<activity
    android:name=".MainActivity"
    android:label="@string/app_name"
    android:launchMode="singleTop">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity
    android:name=".AlbumActivity"
    android:label="@string/album_title"
    android:launchMode="singleTop"
    android:parentActivityName=".MainActivity" />
<activity
    android:name=".TrackActivity"
    android:autoRemoveFromRecents="true"
    android:label="@string/track_title"
    android:parentActivityName=".AlbumActivity"
    android:taskAffinity=".Track" />

Conclusion

Now that you know how to tune the AndroidManifest to get a satisfying parent navigation, please don’t just finish() the activity when pressing Up 😀

Sources

  1. Github Project
  2. https://developer.android.com/training/implementing-navigation/temporal
  3. https://developer.android.com/training/design-navigation/ancestral-temporal
  4. https://developer.android.com/guide/components/tasks-and-back-stack.html
  5. https://developer.android.com/design/patterns/navigation.html
Did you enjoyed this post? Join Drivy's engineering team! View openings