This is a new series of articles called “Modern Android Development Tips, “or “MAD Skills” for short. Stay tuned for this series of articles dedicated to helping developers build better modern Android development experiences.
Today is the second article in this series: Navigation to dialog Destinations. If you want to see the first article in this series: Overview of Navigation Components, click here.
An overview of
In the last article in this series, I gave you an overview of navigation components and how to use navigation diagrams.
In this article, I’ll show you how to navigate to dialog destinations using the API. Most navigation takes place between Fragment destinations, and the fragments are replaced inside the NavHostFragment object in the UI. But it is also possible to navigate to destinations outside the container including dialog boxes. Just as we implement normal destinations, we can use navigation diagrams to navigate to dialog destinations.
Donut recording app
I have one small problem: I love donuts.
I want to remember which doughnuts I ate were good so I can buy them again. And for those I don’t like, I can avoid buying them again. But I’m forgetful, so the question is, how can I record such important data?
I got it: I’m going to use an app!
Unfortunately, I couldn’t find a single donut Recording app in the Play store (amazing). So I had to write my own app. The app will have a list of all the donuts I’ve eaten, along with information I’ve recorded about each of them, such as name, introduction, and maybe a photo with a rating.
This will be a fairly simple application, consisting of two pages:
- A doughnut list page
- A form page that allows you to enter information about donuts that I want to add to the list or that I want to edit from an existing list of donuts
As for the information edit page, I would like to use a dialog box. I wanted to implement a relatively lightweight popover on the current activity, rather than replacing the entire page. I know the navigation component can handle destinations, but that can only replace fragments within a single NavHostFragment, right?
Yes and no. The default behavior of the navigation component is indeed to replace the fragment in NavHostFragment. But the navigation component can also handle dialog box destinations outside of navHostFragments.
Create a project from a template
First, I’ll show you how to set up the basic elements of navigation in a new application. Then, I’ll show you the donut recording app I’ve written so you can get a sense of what it’s going to be like. (I call this the Julia Child technique. On her cooking show years ago, Ms. Child would introduce the recipe first, followed by a quick demonstration of the finished product, and then the tedious work of preparation and cooking.)
Since Android Studio 3.6, you can choose any new project template to use the navigation component. I find this handy, and even if my final interface looks nothing like the template app, at least the template takes care of things like downloading the appropriate dependencies and creating the base code and resources.
To start, we need to create a Basic Activity in Android Studio. I covered this step in my last article, and you can check it out for more details. Here we will skip straight to the next step.
Dialog box destination
If you look at our new Basic Activity in the navigation diagram, you will see that the application now has two destinations, including actions that jump between them. Both destinations are fragments, and the template allows us to replace them inside navhostFragments.
Basic Activities come with two fragments and operations to navigate between them
That’s basically all we need, except that the destination is a dialog box where we can enter doughnut details. To create this destination, we first create the dialog box classes we need.
First, we create a layout with text placeholders in the UI. Create a file named my_dialog.xml under the Layout Resources folder. Then within the layout, add a TextView and restrict its margins to keep it in the middle of the container. The result should look like this:
We create a simple dialog box that includes a centered text placeholder
Next, create a Fragment to load the layout created above. Create a new Kotlin file in the main package and name it mydialog.kt. In the file, create a subclass inherits from BottomSheetDialogFragment MyDialog, and rewrite the onCreateView () to return a load since we just created the layout of the view.
class MyDialog : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup? , savedInstanceState:Bundle?).: View? {
return inflater.inflate(R.layout.my_dialog,
container, false)}}Copy the code
Now that we have the dialog fragment, we can create a destination to navigate to. Let’s go back to the navigation diagram and add a new destination. On the menu that pops up, you should see that the system has recognized MyDialog, select it.
Select MyDialog from the list as the new destination and make sure it is a “dialog” and not a “Fragment”
Observant readers may spot a bug in the IDE in the screenshot above. Although MyDialog is actually a Dialog object, navigation tools sometimes fail to recognize it accurately and add it as a Fragment destination. This result is by no means what we expected. While it doesn’t happen very often (ok, unexpected results), this problem has cropped up many times during my development of this example, so I want to highlight it here. It’s really very confusing. Fortunately, the solution is very simple, so you just need to know that it can happen.
If you run into this problem, you can solve it by simply going to the NAVIGATION diagram’s XML code and changing the Fragment tag to dialog. Here’s the code after I solved the problem:
<dialog
android:id="@ + id/myDialog"
android:name="Com. Android. Samples. Navdialogsample. MyDialog"
android:label="MyDialog" />
Copy the code
In addition, I have consulted with the Android Studio team on this issue. The problem is said to be caused by the order in which internal dependent searches are performed. They are fixing the problem.
Now that the dialog box destination is ready, we can create an action that jumps from the main screen to the dialog box destination:
Create a new action to navigate from FirstFragment to dialog
We need an additional step to navigate to this dialog. In the FirstFragment code, there is a code (automatically created by the Basic Activity template) that handles the button click event and navigates to the SecondFragment destination:
view.findViewById<Button>(R.id.button_first).setOnClickListener {
findNavController().navigate(
R.id.action_FirstFragment_to_SecondFragment)
}
Copy the code
We simply change the navigation destination to the dialog box with the appropriate ID, which is generated when the destination is created in the navigation diagram.
view.findViewById<Button>(R.id.button_first).setOnClickListener {
findNavController().navigate(
R.id.action_FirstFragment_to_myDialog)
}
Copy the code
And you’re done! Finally, we can run our application and see it in action. When we click the button, it will take us to the dialog box destination as scheduled.
Clicking the button opens a very small dialog box with text placeholders
You may notice that the dialog appears much smaller than it looks in the designer tools-this is because the dialog only has the TextView placeholder as its content. But trust me, that’s our dialog box.
What we’ve just created is a relatively simplified version of the donut recording app I wanted, just to show you the basic steps of how to create and use dialogs as destinations. Next, let’s look at the actual code for the Doughnut application.
DonutTracker application practice
“Spoiler alert” : I’ve finished writing the DonutTracker app. I’ll walk you through the key implementation steps and you can see how I use the dialog for destination navigation.
First, here’s the app’s navigation:
There are two destinations in the DonutTracker navigation diagram
You’ll notice that the home page destination still exists, but it’s called the donutList. This is the fragment that contains the doughnut list (use RecyclerView). I also created a second destination, called the donutEntryDialogFragment, which allows users to edit doughnut information.
If we look at the code for DonutList, which contains the RecyclerView that displays the list data, we can see how navigation is handled. Clicking the FloatingActionButton (FAB) button triggers the navigation to the dialog:
binding.fab.setOnClickListener { fabView ->
fabView.findNavController().navigate(DonutListDirections
.actionDonutListToDonutEntryDialogFragment())
}
Copy the code
Notice that I’m using a view binding to get a reference to FloatingActionButton, which is a reference to binding.fab.
In addition, we can also see in this file how clicking the RecyclerView list item navigates to the edit item dialog:
donut ->
findNavController().navigate(DonutListDirections
.actionDonutListToDonutEntryDialogFragment(donut.id))
Copy the code
There are a few things to note about the code snippet above:
First of all, The navigate() function we use here (navigated with Directions object) has the same syntax as the one we created earlier with the Basic Activity template (navigated to a dialog via R.I.D.A. ction_FirstFragment_to_myDialog) The specified operation) is slightly different. This is because the code snippet above came from the final version of the DonutTracker application, where I used SafeArgs. SafeArgs can generate Directions code, which makes it easier to implement jumps between destinations with parameter passing.
Second, we call the navigate() method differently when navigating from FAB (without passing parameters to the Directions object) than when navigating from any of the items in the doughnut list (passing donut.id). This distinction allows us to decide whether to create a new donut (when no parameter is passed) or edit an existing donut (when donut.id is passed). (Spoiler alert: I’ll cover this topic in an upcoming article, and you can check out the full code in the meantime.)
Running the application shows how it works. As you can see, I’ve pre-entered some important doughnut data into the app:
The DonutTracker app displays a tantalizing list of donuts
Click FAB to see a dialog box for new doughnut information:
Click FAB to navigate to the dialog box destination for entering new doughnut information
If we click on any of the existing donuts (I clicked on “fundonut” here, because the description obviously needs some polishing), the app takes us to the same dialog destination where we can edit the information about the doughnut we just clicked on.
Clicking on any doughnut navigates to a dialog box to edit its information
Clicking the DONE button saves the changes to the database and returns the updated list; Clicking the CANCEL button abandons all edits and returns. Note: Clicking the back button also returns the doughnut list, because the navigation component has automatically set the back stack for us.
conclusion
In this article we learned how to quickly create a new application using the built-in navigation components and learned how to navigate to dialog box destinations. In the following articles, we will continue to develop the application to show you the other features of the navigation component, while also implementing a more powerful doughnut recording application.
For more information
For more details on navigation components, see the Getting Started documentation on navigation Components
For the complete code for the DonutTracker application, see the Github example
For more on MAD Skills, check out the Android Developers channel