Navigation Component for Android Part 3: Transition and Navigation
In this tutorial, you’ll learn how to use shared element transitions, action bar and bottom navigation to make an app that shows a list of random dogs images. By Ricardo Costeira.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Navigation Component for Android Part 3: Transition and Navigation
25 mins
- Getting Started
- Fetching Dependencies
- Sharing Elements Between Screens
- Adapting the Adapter
- Adding Shared Elements to NavController
- Walking the Doggo to Another View
- Teaching the RecyclerView to Stay
- Controlling the Action Bar
- Adding a Menu Item
- Implementing Bottom Navigation
- Updating Styles and Layouts
- Wiring the NavController to Bottom Navigation
- Fixing The Action Bar Navigation Button
- The Multiple Back Stacks Problem
- Where to Go From Here?
Navigation is an essential part of Android development. The most common way to do it is via Intents and Fragment transactions. This works great for simple cases but, as the framework evolved, handling navigation became harder with more complex UI designs.
Fragments inside Fragments, deep links, bottom navigation bars, navigation drawers… You probably just felt a shiver down your spine picturing yourself handling a few of these together with just Intents and Fragment transactions.
Jetpack’s Navigation Component is Google’s attempt to earn back the Android developer’s love. Those examples given above? Navigation Component can handle all of them at the same time with a few lines of code.
It’s becoming a must have in any Android developer’s skill set!
In this tutorial, you’ll learn use Navigation Component for:
- Shared element transitions.
- Controlling the Action Bar.
- Handling bottom navigation.
As you explore Navigation Component, you’ll work on an app named My Little Doggo. It’s an app that shows you a list of random dog images and lets you mark your favorites.
OK, time to look at some cute dogs. :]
Getting Started
Download the project materials by clicking the Download Materials button at the top or bottom of this tutorial.
Launch Android Studio 3.6 or later and select Open an existing Android Studio project. Then navigate to and select the starter project folder. You’ll see a structure like this:
Explore the project for a bit. Focus on the presentation package, where all the UI related code resides.
Then, go to the res package. You’ll see there’s already a package called navigation with three different nav graphs. The main nav graph is app.xml. Its only purpose is to nest the other two, doggo_list and favorites.xml, which you’ll work with later in this tutorial.
Build and run the project. You’ll see a simple screen with a list of doggos. The app already uses a simple implementation of Navigation Component that lets you click a doggo to see it in full screen.
This is a great app to show to any UX designer, but it has some problems:
- Action Bar doesn’t update.
- Full screen view just snaps in.
- Button can’t navigate to your favorites.
It’ll take some work, but the doggos will keep you company every step of the way. :]
Fetching Dependencies
To kick things off, you need to add some dependencies:
- Material Components: Needed for transitions and bottom navigation.
- Navigation UI: Responseable for handling the action bar, bottom navigation and navigation drawer.
Head to the app build.gradle. At the bottom, right below the navigation dependency, add these two lines:
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation "com.google.android.material:material:$material_version"
The versions are already in the root build.gradle. Sync Gradle and run the app to make sure everything is OK.
Sharing Elements Between Screens
Shared element transitions are a neat way to express continuity and flow smoothly between screens. You can use them to help the user better understand the flow of information when you have common elements between screens.
To use shared element transitions, you need to define which views transition between screens and give each of those transitions a unique name.
Since the app uses Fragments to navigate, you have to tinker with Fragment transitions by postponing them and starting them later at the right time so they don’t interfere.
Adapting the Adapter
Expand presentation and open DoggosAdapter.kt.
As mentioned before, each shared element transition needs a unique name. In this case, the picture URL is a good option. In bind()
method, add the following line below load(item.picture){...}
code block:
transitionName = item.picture
This sets the transitionName
to the corresponding Doggo
picture url.
That’s all for the Adapter.
Well done! Build and run the app to make sure you didn’t break anything. The app will launch and behave as before.
You won’t see any differences in the animation yet. You still need to tell Navigation Component which views it should transition.
Adding Shared Elements to NavController
Now, click the doggos package and open DoggoListFragment.kt.
First, add a helper method right at the bottom of the class, which will be used to simplify navigation:
private fun navigate(destination: NavDirections, extraInfo: FragmentNavigator.Extras) = with(findNavController()) {
// 1
currentDestination?.getAction(destination.actionId)
?.let { navigate(destination, extraInfo) //2 }
}
Here is what is happening in this function:
- Make sure the current destination exists, by checking if the passed destination’s action id resolves to an action on the current destination. This avoids attempting to navigate on non-existent destinations
- Passing
FragmentNavigatorExtras
instance with the extra information intoNavController
through itsnavigate
method.
Now to use it, navigate to method called createAdapter()
. This method creates the Adapter for the RecyclerView and is where you pass a lambda to DoggosAdapter
.
Delete the findNavController().navigate(toDoggoFragment)
line and add the following in its place:
//1
val extraInfoForSharedElement = FragmentNavigatorExtras(
//2
view to doggo.picture
)
//3
navigate(toDoggoFragment, extraInfoForSharedElement)
Here’s a code breakdown:
-
FragmentNavigatorExtras
is the class you use to tell Navigation Component which views you want to share, along with the corresponding transition name, which you previously set as the picture URL. - Writing
view to doggo.picture
is the same as writingPair(view, doggo.picture)
. - In this code, you pass the created
FragmentNavigatorExtras
instance with the transition information into the helper method created earliernavigate
method.
You’re halfway done!
Build and run the app. You still won’t see any difference in the animation, but the app would run and behave like before.
Next, you’ll tell the destination Fragment that there’s an element to transition.
Walking the Doggo to Another View
You need to define which kind of transition you want the Fragment to do. So, you’ll create a transition and set it to the Fragment’s sharedElementEnterTransition
property.
First, right-click the res package and select New ▸ Android Resource Directory. On the Resource type dropdown, select transition. It’ll automatically change the directory name:
Now, click OK. You’ll see a transition directory inside res.
Next, right-click transition and select New ▸ Transition resource file. Call it shared_element_transition and click OK. Then delete everything inside and paste the following:
<?xml version="1.0" encoding="utf-8"?>
<changeBounds xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/fast_out_slow_in" />
You have your transition defined. Now, hand it to the destination Fragment.
First, expand the doggodetail package and open DoggoFragment.kt. Looking at the code, you can tell DoggoFragment
already uses Navigation Component.
It fetches the doggo’s picture URL and favorite status through navArgs()
and uses them to set up its UI.
Now, at the bottom of DoggoFragment
, add the following method:
private fun setSharedElementTransitionOnEnter() {
sharedElementEnterTransition = TransitionInflater.from(context)
.inflateTransition(R.transition.shared_element_transition)
}
The app will crash if you import the wrong dependency! If androidx.transition
doesn’t appear, it means you didn’t import Material Components. Go to the Fetching Dependencies section of the tutorial to see how it’s done.
TransitionInflater
, be sure to import TransitionInflater
from androidx.transition
and not android.transition
.
The app will crash if you import the wrong dependency! If androidx.transition
doesn’t appear, it means you didn’t import Material Components. Go to the Fetching Dependencies section of the tutorial to see how it’s done.
You’re setting sharedElementEnterTransition
in the method, but you’re not calling it anywhere. That won’t do.
Fix this by calling setSharedElementTransitionOnEnter()
and postponeEnterTransition()
in onViewCreated()
:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val (picture, isFavorite) = args
// Add these two lines below
setSharedElementTransitionOnEnter()
postponeEnterTransition()
setupFavoriteButton(picture, isFavorite)
image_view_full_screen_doggo.load(picture)
}
You already know what setSharedElementTransitionOnEnter()
is for, but what’s with postponeEnterTransition()
?
Well, the images you want to transition are loaded into the Fragment view by Glide. Loading images takes time, which means that the view runs its transitions before the image is available, messing up the animation. The trick here is to postpone the Fragment enter transition and resume it after the images finish loading.
Pretty clever, huh?
To do this, you’re going to take advantage of the fact that Glide lets you add request listeners to its image loading.
First, add the following method at the bottom of DoggoFragment
, right below setSharedElementTransitionOnEnter()
:
private fun startEnterTransitionAfterLoadingImage(
imageAddress: String,
imageView: ImageView
) {
Glide.with(this)
.load(imageAddress)
.dontAnimate() // 1
.listener(object : RequestListener<Drawable> { // 2
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: com.bumptech.glide.request.target.Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
startPostponedEnterTransition()
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: com.bumptech.glide.request.target.Target<Drawable>,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
startPostponedEnterTransition()
return false
}
})
.into(imageView)
}
Now, resolve all the reference errors. When multiple imports are possible, be sure to pick the ones prefixed with com.bumptech.glide
.
This code is the basic Glide usage with two extra calls:
- You don’t want Glide to mess up things with its default crossfade animation. As such, you call
dontAnimate()
to avoid it. -
RequestListener
needs you to overrideonLoadFailed
andonResourceReady
. You callstartPostponedEnterTransition()
in both of them because you need to even if the request fails. If you don’t call it, the UI will freeze after callingpostponeEnterTransition()
.
Your transition is almost ready!
Now, go back to onViewCreated()
in DoggoFragment.kt and replace image_view_full_screen_doggo.load(picture)
with:
image_view_full_screen_doggo.apply {
//1
transitionName = picture
//2
startEnterTransitionAfterLoadingImage(picture, this)
}
Here you:
- Let the image know it has a transition by setting its
transitionName
. - Call the method to load the picture into the ImageView and resume the enter transition after the load finishes.
The shared element transition is now complete. Whew!
Build and run, then give the app a whirl. Congrats on your cool transitions!
Wait, what? Where’s the return transition? Did a doggo run away with it?