Mastering Android Dialogs: Don’t follow official Google Guides
This article explains why Google Guides for Dialogs are bad and what risks you and your apps may face if you follow them.
This article is part of a multi-series on Mastering Android Dialogs
- Don’t follow official Google Guides
- Using Coroutines to show and get results from Dialogs easily
COMING SOON
Intro
Callbacks, listeners, onActivityResults - these are spread all over Android APIs, and frankly speaking, it drives me crazy. Every time you want to ask for permission, take a picture, choose a file, or even show a simple dialog you have to deal with them. After some time I feel more like playing ping-pong in Android than actually writing readable, maintainable, and, what’s very important, REUSABLE code.
Do we have to use them? Is there a better way? Of course, there are lots of better ways and countless solutions to the same problem - as usual in Android. This article explains why Google Guides for Dialogs are bad and what risks you and your apps may face if you follow them.
One last thing before we start
✉️ Android Dev Newsletter
If you enjoy learning about Android like I do and want to stay up to date with the latest, worth-reading articles, programming news, and much more, consider subscribing to my newsletter 👇
🎙 Android Talks Podcast
If you’re a Polish speaker and want to listen to what I have to say about Android, architecture, security, and other topics, check out my podcast 👇
https://androidtalks.buzzsprout.com
What is currently recommended by Google?
Let’s look into the official documentation as of November 2022 and check out what Google recommends when dealing with dialogs. The most common places to find information about it are the guides below.
- Dialogs UI Guide https://developer.android.com/develop/ui/views/components/dialogs
- Displaying dialogs with DialogFragment Guide https://developer.android.com/guide/fragments/dialogs
- Create destinations https://developer.android.com/guide/navigation/navigation-create-destinations
- Interact programmatically with the Navigation component https://developer.android.com/guide/navigation/navigation-programmatic
The guides are not very long, to be honest. They are very deficient. I could imagine someone who’s just started learning Android and wants to get into Dialogs, but he only finds a few simple examples, not fitting into most real-world apps. Unfortunately, that’s the case for most guides and samples.
Showing a Dialog
We’ll start with something simple. The first action we’ll consider is simply to show a dialog. Most of the time you’ll find yourself dealing with two scenarios.
Scenario I - Showing a simple, built-in dialog
The quickest way to show a dialog is to use one of the built-in ones, like AlertDialog
, DatePickerDialog
, TimePickerDialog
, etc. These can be quickly instantiated and shown using Builders.
Google also claims that
The AlertDialog
class allows you to build a variety of dialog designs and is often the only dialog class you'll need.
Well, based on my experience and many apps I used, I'd say designers in most projects prefer to create custom dialogs rather than using AlertDialog
. Nevertheless, if you don’t have time and want to display something simple, I would say it’s a good choice.
Here’s a quick reminder of how to show AlertDialog
:
The most important part of this code is the first line - usage of context in the Builder constructor. If you have it, you can show the dialog.
And here’s an example of what such dialog looks like:

Scenario II - Showing custom dialog with more demanding layouts, logic, changed behaviour, etc.
Using a built-in dialog does not meet your requirements anymore. Maybe you need to create a fully customized dialog? Or maybe you just want to reuse the same dialog in a few places and you don’t want to repeat yourself, so you put the code in a single class. It doesn’t really matter. Most of the time you’ll end up using DialogFragment
anyway.
You can still use built-in dialogs like AlertDialog
inside if you want:
Or create the view from scratch if your designer likes fancy UI:
Showing DialogFragment
Displaying a built-in dialog is easy, as you saw before. How do you show a DialogFragment
then?
Well, it mainly depends on what kind of navigation you are using in your app. Most of you will probably use:
FragmentManager
- or Jetpack Navigation
Of course, both of them have their advantages and drawbacks, nevertheless, they are still the two most popular navigation libraries in Android.
The first way - FragmentManager
First, let’s figure out how to show it using plain FragmentManager
. Fortunately, Google has already provided an example in their guide 👇
When you want to show your dialog, create an instance of yourDialogFragment
and callshow()
, passing theFragmentManager
and a tag name for the dialog fragment.
Let’s take a closer look at the show(manager, tag)
function.
Here’s what you can find inside 👇
/**
* Display the dialog, adding the fragment to the given FragmentManager. This
* is a convenience for explicitly creating a transaction, adding the
* fragment to it with the given tag, and {@link FragmentTransaction#commit() committing} it.
* This does <em>not</em> add the transaction to the fragment back stack. When the fragment
* is dismissed, a new transaction will be executed to remove it from
* the activity.
* @param manager The FragmentManager this fragment will be added to.
* @param tag The tag for this fragment, as per
* {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
*/
public void show(@NonNull FragmentManager manager, @Nullable String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.setReorderingAllowed(true);
ft.add(this, tag);
ft.commit();
}
You can clearly see that this function is just a wrapper to create and commit the FragmentTransaction
. Everything would be fine if only it had one more important check. You see, every time you want to perform some action on the FragmentManager
, it will first check for state loss. If the state is already saved and you want to execute the action, you will end up with IllegalStateException
.
You can take a closer look at the FragmentManager
code here and see yourself.
/**
* Returns {@codetrue} if the FragmentManager's state has already been saved
* by its host. Any operations that would change saved state should not be performed
* if this method returns true. For example, any popBackStack() method, such as
* {@link#popBackStackImmediate()} or any FragmentTransaction using
* {@linkFragmentTransaction#commit()} instead of
* {@linkFragmentTransaction#commitAllowingStateLoss()} will change
* the state and will result in an error.
*
*@returntrue if this FragmentManager's state has already been saved by its host
*/
public boolean isStateSaved() {
// See saveAllState() for the explanation of this. We do this for
// all platform versions, to keep our behavior more consistent between
// them.
return mStateSaved || mStopped;
}
What could be done to avoid this rather edge case? You have to check if the state was already saved before committing the FragmentTransaction
, here’s an example:
The second way - Jetpack Navigation
The second approach we’ll consider is to use one of Jetpack’s most popular libraries - Jetpack Navigation.
It is mainly admired for its “simplicity” and low entry level by beginners. Also very advertised by the Android Team. Notice that I wrote “simplicity”, mostly because of its two sides.
You will meet the first side when starting to learn it, probably more as a beginner. It will look like it’s easy to use and fits well into your codebase. Then, very likely, sometime later you’ll end up regretting using it in the first place, especially if you’re dealing with apps that feature:
- Many screens
- Reusable flows containing dynamically ordered screens
- Advanced usage of deep links
- Dynamic navigation
- etc.
If I had to pick one, I would choose to use FragmentManager
over Jetpack Navigation, but still, I think it is fine to be used in small CRUD or sample apps.
Let’s return to our task - displaying the dialog. Unfortunately, there’s no Guide dedicated to Jetpack Navigation and Dialogs, but you can find some information about creating a destination from a DialogFragment
here and in this video.
Like most of the time, you can do it in many ways. Here’s one of them that allows you to create a reusable dialog and show it from multiple places in the app.
Step I - Create a new nav graph for the dialog and its arguments.
Step II - Include your dialog’s nav graph to the main nav graph (ex. hosted by your Activity).
Step III - Create an action to the dialog’s nav graph where you want to show it. Notice that you have to define arguments again.
Step IV - Use the action and navigate to the dialog.
Summary of showing dialogs
A quick sum-up before we move on. You saw how to display both built-in and custom dialogs. Unfortunately, you also saw that Google forgets and is sometimes not aware of some things when preparing these guides, this time when using FragmentManager
. The back stack itself is already error-prone and had many issues in the past (well, it still has). That’s why it’s crucial to have your eyes wide open when working with navigation, lifecycle, etc.
When it comes to Jetpack Navigation, I only gave you a small example, but you can already see the darker side of it. Showing a DialogFragment
requires you to have an additional nav graph and include it to each nav graph from which it is to be displayed later. It also forces you to copy-paste the dialog arguments to every action that launches it. That is completely unnecessary from my perspective and only introduces more errors to your app. Furthermore, nav graphs are very strict and static. They are not very dev-friendly when it comes to dynamic navigation, reusing whole Fragments in different flows, etc.
Another issue for me is that we’re only able to display dialogs when we have access to either Context
or NavController
. This means that if you want to display it from the VM (based on some logic) or UseCase / Whatever that launches a flow you want to reuse in dozens of places you’ll end up with a rather unsatisfying implementation and lots of callbacks and ping-pong code, even when trying to figure out what was the dialog’s result.
Want to know more about the callback hell and Android ping-pong architecture? Continue reading 👇
Getting results from dialogs
Dialogs, popups, modals, bottom sheets, etc. are very important in our apps. They allow you to display info, ask for user input, and perform actions. What matters the most is not only how to properly scope it and show it, but also receive the result and be able to handle it. Just like before, we’ll consider two scenarios.
Scenario I - Getting results from a simple, built-in dialog
Unfortunately, there’s nothing about getting results from dialogs like AlertDialog
in official guides. Maybe Google doesn’t really care about them or doesn’t recommend using them without DialogFragment
? Nevertheless, how can we do it?
The quickest way would be to introduce the callback hell, like below:
Notice that adding the callbacks was easy and it looks like you can receive the result and run some code on Ok and Cancel actions. The main problem here is that you don’t have any control over when the dialog ends, when the result comes back, and when the Code B block starts running.
It would be much better if we could write the same code like this:
Scenario II - Getting results from DialogFragments
Things get more complicated if you want to receive the result from a custom dialog. Since you’re creating a new Fragment
and launching it “outside” the caller, you’re no longer able to pass a callback.
Of course, it is doable technically, but because you’re not using the Bundle
to pass arguments you will end up with errors when encountering situations like a configuration change, state saving, etc. So please, don’t even try to do that.
Just like before, we will consider two kinds of navigation in our app:
FragmentManager
- and Jetpack Navigation.
The first way - FragmentManager
First, we’ll consider using plain FragmentManager. Luckily for us again, Google has prepared some examples that you can check out here 👇
When the user touches one of the dialog's action buttons or selects an item from its list, your DialogFragment
might perform the necessary action itself, but often you'll want to deliver the event to the activity or fragment that opened the dialog. To do this, define an interface with a method for each type of click event. Then implement that interface in the host component that will receive the action events from the dialog.
Okay, we got some instructions.
Just to clarify what Google is suggesting here:
1) Google treats dialog results as events, not results. This is rather misleading since you can have either a button clicked or a result with data returned, so overall I think the result naming fits better here.
2) For each dialog that needs to send back the events (results) back to its host, define an interface with a method for each type of event (result). So, if you have 10 dialogs in your app, you need to create and maintain 10 interfaces.
3) Then, wherever you want to launch the dialog and get the result back, implement the interface and override the methods. Here’s where the callback hell is starting to show up. Just imagine the ping-pong game between the Fragment
and the ViewModel
that wants to proceed with its logic based on results from dialogs. Also, don’t forget that if you want to display 3 dialogs on one screen you have to implement 3 interfaces in your Fragment
and override all methods inside.
A quick example of how this could look like (source) 👇
One of the dialogs with a listener interface:
And the Activity hosting 3 dialogs:
This is a slightly modified version of what is originally posted in the Dialog Guide, just to show you what you end up with if you have more than 1 dialog to be displayed. By the way, why do they pass the DialogFragment back to the host when the result is returned in the example? I have no idea what’s the point of that, but that’s completely unnecessary.
The second way - Jetpack Navigation
Now it’s Jetpack Navigation’s turn again. We already covered how unnecessarily complicated it is just to show a dialog using it. Will it be the same when getting the results?
Sadly, the only information about passing results back that you can find is available here in the “Returning a result to the previous Destination” section. What they’re suggesting is to access the previous NavBackStackEntry
and save the result into the key-value map in SavedStateHandle
.
These values persist through process death, including configuration changes, and remain available through the same object. By using the given SavedStateHandle
, you can access and pass data between destinations. This is especially useful as a mechanism to get data back from a destination after it is popped off the stack.
Sounds reasonable. Let’s see it in action.
That’s what you would do to save the data and pass it back:
And that’s what you would do to retrieve the result:
Summary of getting results from dialogs
What did we learn about the recommended ways of getting results back from dialogs? Well, for sure we learned that it is far more complicated than it should be. The tables have turned completely.
Retrieving the results with FragmentManager
using the recommended approach is not what I would expect. Creating unnecessary interfaces, implementing them, overriding all methods, every damn time - this leads to boilerplate code repeated in many screens, dealing with the callback hell, and playing ping-pong all the time.
This time, if I had to pick one solution I would go with what Jetpack Navigation offers. I still don’t like it very much, but in this case, it seems more reasonable and requires much less code to write and repeat.
What would be the ideal way of showing and getting the result from dialog then?
Same example as I’ve shown before:
Combining dialogs with Coroutines can save us a lot of time, code and struggle. What we want is just to show the dialog and await for result wherever it was called from. Additionally, you should be able to queue the dialogs and dismiss them if the Coroutine is cancelled.
Do all that and suddenly your dialogs may be used like this:
Or this:
Or in many other cool ways that you would not be able to put if you followed the recommended approach.
Conclusion
Hopefully, this article helped you understand a few things about dialogs and different methods of controlling them. Just for clarification, I’m not saying that Google Guides are useless. They are there mostly for you to learn the basics of doing certain things in Android, nevertheless, they can be harmful to bigger codebases and you have to be aware of that.
Every app is different and has certain requirements that others don’t. Keep that in mind and think twice before using any new kinds of navigation or before following any Guide, even this article! 😄
Leave your comments below if I forgot about something or if you just want to share your thoughts. If you liked this article, be sure to follow me to be notified about the next one, where I’ll cover “Using Coroutines to show and get results from Dialogs easily”.
Thanks for reading 👋