In this article, I'll tell you how to use the new Material dialogs with DialogFragment
.
Material dialogs are a re-styling of commonly-used Dialog
. I won't go into too many details on what they do or how to customize them here, you can read this great article from Nick Rout: Hands-on with Material Components for Android: Dialogs.
Here is, for comparison, an AppCompat AlertDialog
and a Material AlertDialog
:
You can see the differences in default shadow, size, typography, etc... But the real advantage is to be able to support Material typography and shape out of the box with theme configuration.
For example, here is the result with bold title and custom shape (You can look into styles.xml for how to do this):
Nice isn't it? Except it is quite limited to use only Dialog
. You can't use complex layouts other than the 4 types mentioned in Nick's article, or use them with more complex view hierarchy, or from Navigation Architecture Component. For all that, you will have to use a DialogFragment
.
If you try to display a DialogFragment
now, we will see it still looks like a regular AlertDialog
:
So let's move onto the solution on how to use Material dialog with DialogFragment
!
The solution:
DialogFragment
s are useful to show more complex layouts in a dialog way, allowing you to control and manipulate your custom layout. They act like regular Fragment
, meaning you have to override onCreateView()
& onViewCreated()
to display your custom view.
To use Material dialogs with DialogFragment
, you can override onCreateDialog()
in your DialogFragment
, to return a MaterialDialog
. It is mentionned briefly by Nick in their article, you can see the example on this commit. So implement it, show it and you get:
...Nothing. How is this possible?
It's because DialogFragment
ignores onCreateView()
if you override onCreateDialog()
, as it assumes the dialog will take care of its own view. As the docs say:
when doing so,
onCreateView(LayoutInflater, ViewGroup, Bundle)
does not need to be implemented since the AlertDialog takes care of its own content.
The solution would be to use setView()
on our MaterialDialogBuilder
inside onCreateDialog
. You can check the example at this commit and it works! 🎉
The problem with this approach, is that for complex layouts it is difficult to manage as we would have to handle all communications with our views when creating the Dialog
, which is not very clean.
It can also cause sneaky crashes, as getView()
on our DialogFragment
will return null
, since the view wasn't properly initialized this way. It also breaks ViewBinding
or Kotlin synthetics, which may require painful migrations on larger codebases.
So is there a better way?
The hack:
It turns out we have pretty much everything we need at hand to make it work without needing to migrate existing code.
In a new class inheriting DialogFragment
, we can override onCreateDialog()
to make it use onCreateView()
& onViewCreated()
to display and manage the Fragment
's layout, like so:
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(requireContext(), theme).apply {
dialogView = onCreateView(LayoutInflater.from(requireContext()), null, savedInstanceState)
setView(dialogView)
}.create()
}
Note that we don't need to pass a proper container
here to onCreateView
since this is used when the fragment will be added to a view hierarchy, which is rarely the case for a DialogFragment
. We also properly restoring the state with savedInstanceState
.
We can then override the getView()
for our Fragment
as to fix accessing our views:
override fun getView(): View? {
return dialogView
}
This basically redirects view accessors to refer to our dialog's custom view, fixing in the process tools making it easier to access views inside Fragment
s.
We are guaranteed that it will be valid for the lifecycle of the Dialog
, and therefore of the DialogFragment
.
And that's it! You can put that into a new MaterialDialogFragment
class, change your DialogFragment
inheritance to MaterialDialogFragment
, and enjoy your hassle-free Material dialogs. You can find the complete complete example here and the MaterialDialogFragment
class here.
Hope it could help you in some way, don't hesitate to comment if you have a question or suggestion. 👋
⚠️ Disclaimer: This was not battle-tested and could break/not handle everything a DialogFragment
does in some situations. Use at your own risk. There is an issue open on this subject, that you can follow here: MaterialComponents Android: Use MaterialDialog with Navigation Component - Issue #540
Top comments (0)