DEV Community

Jimmy Mayoukou
Jimmy Mayoukou

Posted on • Updated on

How to use Material dialogs with DialogFragment

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:

AppCompat Alert Dialog vs Material Dialog
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):

Customized Material AlertDialog

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:

AppCompat DialogFragment

So let's move onto the solution on how to use Material dialog with DialogFragment!

The solution:

DialogFragments 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:

MaterialDialogBuilder with DialogFragment

...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! πŸŽ‰

Customized DialogFragment

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()
    }
Enter fullscreen mode Exit fullscreen mode

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
    }
Enter fullscreen mode Exit fullscreen mode

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 Fragments.

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)