Image for post
Image for post

Navigating to Dialog Destinations

Navigation component isn’t limited to destinations inside of NavHostFragment

Chet Haase
Oct 14 · 9 min read

This is the second in a series of MAD Skills articles about the Navigation component. These articles are based on content that is also explained in video form, as part of the MAD Skills series, so feel free to consume this material in whichever way you prefer (though the code tends to be easier to copy from text than from a video, which is why we offer this version as well).

If you prefer your content in video form, here’s the thing to watch:

Introduction

In the previous episode, I presented a quick overview of Navigation component, including using the navigation graph.

In this episode, I will explore how to use the API to navigate to dialog destinations. Most navigation takes place between different fragment destinations, which are swapped out inside of the NavHostFragment object in the UI. But it is also possible to navigate to destinations outside of that container, including dialogs. We can do this using the navigation graph tool, just like we do for normal destinations.

Donut Tracking

I have a problem: I like donuts. A lot.

I’d like to know which donuts I’ve enjoyed so that I can get them again — and which ones I didn’t, so that I can avoid them. But I have a bad memory. So how can I keep track of this important data?

I know: I’ll use an app!

Unfortunately, I wasn’t able to find a donut tracking app on the Play Store (can you believe it?!?!). So I’ll need to write it myself. The app will have a list of donuts I’ve had, along with information that I record about each of them them, like their name, a description, maybe a picture, and definitely a rating.

It’s going to be a pretty simple app consisting of two screens:

  • A list of donuts
  • A form where I can enter information about a donut, either a new one that I am adding to the list or information about an existing donut in the list which I am editing

For the information-editing screen, I’d like to use a dialog. I want something lightweight that pops up on top of the activity without replacing my whole UI. I know that the Navigation component handles destinations, but those are just fragments that get swapped out inside the single NavHostFragment, right?

Yes… and no. The default behavior with Navigation component swaps out fragments inside NavHostFragment. But Navigation component also handles dialog destinations, which reside outside of the NavHostFragment.

Start from a Template

First I’ll show how to set up the basics of navigation in a new application. Then I’ll show the donut tracking app that I wrote so you can see where it’s all leading. (I call this the Julia Child trick. In her cooking show many years ago, Ms. Child would start the recipe and then quickly follow that up by pulling out the finished dish, skipping that tedious part in the middle where she wrote all of the code to prep and cook the food).

In Android Studio 3.6 and later, you can select one of the New Project templates to give yourself a head start with the Navigation component. I find this helpful, even when my final UI isn’t going to resemble whatever I start with in the template app, because the templates handle the work of pulling in the proper dependencies and creating the infrastructure code and resources.

Let’s do that now and create a Basic Activity in Android Studio. I walked through some of this in the previous article or video, so check that out for more details and visuals if you’d like. Meanwhile, I’ll skip ahead to the next step.

Dialog Destination

When you look at the navigation graph for our basic activity, you can see that the app has 2 destinations, along with actions to take us between those destinations. These destinations are fragments, which will be swapped in and out inside the NavHostFragment that the template created for us.

Image for post
Image for post
Basic Activity comes with two fragment destinations along with actions to navigate between them

This is almost what we need… except the destination we actually want is a dialog in which we will enter details about our donuts. In order to create that destination, let’s first create the dialog class that we need.

First, we’ll create the layout with just a placeholder item in the UI. Create a file called my_dialog.xml in the layout resources directory. In that layout, add a TextView and constrain it to all four sides to center it within that container. The result should look something like this:

Image for post
Image for post
Our simple dialog, with Placeholder text in the center

Next, create the Fragment which will inflate that layout. In the main package, create a new Kotlin file called MyDialog.kt. In that file, create MyDialog to be a subclass of BottomSheetDialogFragment and have it override onCreateView() to return the view inflated from the layout resource we created earlier:

class MyDialog : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.my_dialog,
container, false)
}
}

Now that we have our dialog fragment, we can create a destination that will navigate to it. Go back to the navigation graph and add a destination. In the popup menu, you can see that it knows about MyDialog. Select that.

Image for post
Image for post
Select MyDialog from the list as the new destination… and make sure it’s coming in as a “Dialog” and not a “Fragment”

Observant readers might notice a little IDE bug in the screenshot above. Even though MyDialog is, in fact, a Dialog object, the navigation tool sometimes does not detect that, and adds it as a Fragment destination instead. This is definitely not what we want. It does not always happen (yay, unpredictable outcomes!), but it happened often enough while working on this project that I wanted to highlight it here in case it happens to you. Because… it’s pretty confusing. Fortunately, the workaround is simple, so knowing that it can happen is really the most important thing.

To fix this problem, if you see it, simply go into the XML code for the navigation graph and change the fragment tag to dialog instead. Here’s what my fixed code looks like after I fixed it:

<dialog
android:id=”@+id/myDialog”
android:name=”com.android.samples.navdialogsample.MyDialog”
android:label=”MyDialog” />

[Aside: I asked the Android Studio team about this issue. It is apparently related to the order in which dependencies are searched internally. They are working on a fix.]

Now that we have a destination for the dialog, we can create an action to take us from the home destination to the dialog:

Image for post
Image for post
Create a new action to navigate from FirstFragment to the dialog

There’s one more step to make it possible to navigate to this dialog. For that, we’ll need to go into the code for FirstFragment. In that class, there is code (created by the Basic Activity template) which handles a button click by navigating to the SecondFragment destination:

view.findViewById<Button>(R.id.button_first).setOnClickListener {
findNavController().navigate(
R.id.action_FirstFragment_to_SecondFragment)
}

We are simply going to change that to navigate to the dialog instead, by using the appropriate id, which was created for us when we created the destination in the navigation graph:

view.findViewById<Button>(R.id.button_first).setOnClickListener {
findNavController().navigate(
R.id.action_FirstFragment_to_myDialog)
}

And that’s it! Now we can run our app and see the results . When we click on the Button, it takes us to the dialog destination, as advertised:

Image for post
Image for post
Clicking on the button brings up the [very tiny] dialog with its Placeholder text item

Note that the dialog is showing up a lot smaller than it did in the design tool — that’s because there’s only that little TextView “Placeholder” content to contain and wrap around. But trust me — that’s our dialog.

What we’ve created so far is a very simplified version of what I want for my donut tracking app, just to show the basics of how to create and use a dialog destination. Now let’s take a look at the actual code in the donut app to see what it all looks like in practice.

DonutTracker: the App

Spoiler alert: I’ve already written the DonutTracker app. I’ll take you through the important pieces that show how I use dialog destination navigation.

First, here is the app’s navigation graph:

Image for post
Image for post
DonutTracker has two destinations in its navigation graph

You can see there’s home destination as before, called donutList. This is the fragment which contains the list of donuts (in a RecyclerView). I’ve also created a second destination, donutEntryDialogFragment, which is where the user edits donut information.

If we look at the code in DonutList, which is the fragment containing the RecyclerView for that list of data, we can see how the navigation is handled. A click on the FloatingActionButton (FAB) causes navigation to the dialog

binding.fab.setOnClickListener { fabView ->
fabView.findNavController().navigate(DonutListDirections
.actionDonutListToDonutEntryDialogFragment())
}

Note that I’m using View Binding here to get the reference to the FloatingActionButton, thus the reference to binding.fab.

Elsewhere in that file, we can also see how clicking on one of the items in the RecyclerView navigates us to the dialog to edit the information for that item:

donut ->
findNavController().navigate(DonutListDirections
.actionDonutListToDonutEntryDialogFragment(donut.id))

There are a couple of things to note in the code snippets above.

First, the syntax of using the navigate() function here (navigating using a Directions object) is slightly different than what we saw earlier in the Basic Activity we created (where we navigated to an action, specified by R.id.action_FirstFragment_to_myDialog). This is because we are looking at the code for the final version of the DonutTracker app, which uses SafeArgs. SafeArgs generates the Directions code to make it easier to navigate between destinations with arguments.

Second, there is a difference in the call to navigate() when we navigate from the FAB (where we pass no argument to the Directions object) from when we navigate from one of the donut items in the list (where we call it with donut.id). This difference allows us to either create a new donut item (when passing no argument) or to edit an existing item(when passing donut.id). (Spoiler alert: I’ll cover this topic in an upcoming episode. You can also take a look at the complete code in the meantime.)

Running the app shows how it works. I’ve already pre-populated the app with important donut data, as you can see:

Image for post
Image for post
The DonutTracker app, showing a tantalizing list of items

Clicking on the FAB takes us to the dialog where we can enter information about a new donut:

Image for post
Image for post
Clicking on the FAB navigates to the dialog destination to enter information about a new donut item

But if we click on one of the existing items (here I’ve clicked on “fundonut” because I obviously needed a better description), that takes us to the same dialog destination, where we can edit the data for the clicked-on item:

Image for post
Image for post
Clicking on one of the items navigates to the dialog to edit that item’s information

Clicking on the DONE button saves the changes to the database and returns to the list, while CANCEL abandons the edits and returns. Note that clicking the Back button also takes us back to the donut list, because Navigation component sets up that back stack for us automatically to do the right thing.

Summary

That’s it for this time. We saw how to quickly create a new app with Navigation component built in, and how to navigate to a dialog destination. In future episodes, we’ll continue working with this truly important app to show other capabilities of Navigation component … and to build a more powerful donut tracking application, of course.

For More Information

For more details on Navigation component, check out the guide Get started with the Navigation component on developer.android.com.

To see the finished DonutTracker app (which contains the code outlined above, but also the code covered in future episodes), check out the GitHub sample.

Finally, to see other content in the MAD Skills series, check out the video playlist in the Android Developers channel on YouTube.

Android Developers

The official Android Developers publication on Medium

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store