72

Is it possible to use the new Navigation Architecture Component with DialogFragment? Do I have to create a custom Navigator?

I would love to use them with the new features in my navigation graph.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
Leonardo Deleon
  • 2,577
  • 5
  • 15
  • 22

10 Answers10

90

May 2019 Update:

DialogFragment are now fully supported starting from Navigation 2.1.0, you can read more here and here

Old Answer for Navigation <= 2.1.0-alpha02:

I proceeded in this way:

1) Update Navigation library at least to version 2.1.0-alpha01 and copy both files of this modified gist in your project.

2) Then in your navigation host fragment, change the name parameter to your custom NavHostFragment

<fragment
    android:id="@+id/nav_host_fragment"
    android:name="com.example.app.navigation.MyNavHostFragment"
    app:defaultNavHost="true"
    app:navGraph="@navigation/nav_graph"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/toolbar" />

3) Create your DialogFragment subclasses and add them to your nav_graph.xml with:

<dialog
    android:id="@+id/my_dialog"
    android:name="com.example.ui.MyDialogFragment"
    tools:layout="@layout/my_dialog" />

4) Now launch them from fragments or activity with

findNavController().navigate(R.id.my_dialog)

or similar methods.

Daniel
  • 2,415
  • 3
  • 24
  • 34
MatPag
  • 41,742
  • 14
  • 105
  • 114
  • 1
    I have tried with last version and it is working but animations are not running for me. I have to set them in dialog class. Is this happening to someone too? – J.S.R - Silicornio May 21 '19 at 17:19
  • 23
    How to handle its Positive & negative click listener. – Pinakin Kansara Jul 22 '19 at 09:32
  • 1
    @Pinakin you handle results directly in your custom `DialogFragment`, for example using the `onCreateDialog` and setting a `setPositiveButton` and `setNegativeButton` listeners – MatPag Mar 09 '20 at 16:54
  • can I implement that for TimePickerFragment? – Rizki Oktavia Ningrum Apr 13 '20 at 01:50
  • 3
    @Pinakin if you are using navigation to go to a dialog fragment, think of it like navigating to any other fragment. How would you get a result back from navigating to a regular fragment? https://developer.android.com/training/basics/fragments/pass-data-between If the dialog is "owned" by the fragment that started it and you want click listeners you may be better off using a `Dialog` and skipping navigation. – Victor Rendina Jul 29 '20 at 12:50
18

No, as of the 1.0.0-alpha01 build, there is no support for dialogs as part of your Navigation Graph. You should just continue to use show() to show a DialogFragment.

ianhanniballake
  • 191,609
  • 30
  • 470
  • 443
  • 1
    Feature request for Navigation can be directed to the [public issue tracker](https://issuetracker.google.com/issues/new?component=197448) - make sure to state your use case and why you'd find dialogs in your Navigation Graph useful. – ianhanniballake May 13 '18 at 00:34
  • 1
    Is there any update since the spring on the plan for dialogs? – AdamHurwitz Aug 23 '18 at 19:34
  • 4
    @AdamHurwitz - feel free to star the [existing issue](https://issuetracker.google.com/issues/80267254) for updates. – ianhanniballake Aug 23 '18 at 20:16
  • 37
    Accepted answer is no longer valid as `DialogFragment` is now supported with `2.1.0-alpha03` version – musooff Aug 03 '19 at 15:45
  • You just need change your DialogFragment in NavGraph as Razor's answer below! – Andy Oct 23 '20 at 15:29
  • Diaglog destinations are now supported and available in navigation ui component – Hashir Labs Jun 14 '21 at 13:06
5

Yes it is possible, You can access view of parent fragment from dialog fragment by calling getParentFragment().getView(). And use the view for navigation.

Here is the example

Navigation.findNavController(getParentFragment().getView()).navigate(R.id.nextfragment);
Vasily Kabunov
  • 6,511
  • 13
  • 49
  • 53
Niyas
  • 717
  • 11
  • 18
5

Yes. The framework is made in such a way that you can create a class extending the Navigator abstract class for the views that does not come out-of-the box and add it to your NavController with the method getNavigatorProvider().addNavigator(Navigator navigator)

If you are using the NavHostFragment, you will also need to extend it to add the custom Navigator or just create your own MyFragment implementing NavHost interface. It's so flexible that you can create your own xml parameters with custom attrs defined in values, like you do creating custom views. Something like this (not tested):

@Navigator.Name("dialog-fragment")
class DialogFragmentNavigator(
        val context: Context,
        private val fragmentManager: FragmentManager
) : Navigator<DialogFragmentNavigator.Destination>() {

    override fun navigate(destination: Destination, args: Bundle?,
                          navOptions: NavOptions?, navigatorExtras: Extras?
    ): NavDestination {
        val fragment = Class.forName(destination.name).newInstance() as DialogFragment
        fragment.show(fragmentManager, destination.id.toString())
        return destination
    }

    override fun createDestination(): Destination = Destination(this)

    override fun popBackStack() = fragmentManager.popBackStackImmediate()

    class Destination(navigator: DialogFragmentNavigator) : NavDestination(navigator) {

        // The value of <dialog-fragment app:name="com.example.MyFragmentDialog"/>
        lateinit var name: String

        override fun onInflate(context: Context, attrs: AttributeSet) {
            super.onInflate(context, attrs)
            val a = context.resources.obtainAttributes(
                    attrs, R.styleable.FragmentNavigator
            )
            name = a.getString(R.styleable.FragmentNavigator_android_name)
                    ?: throw RuntimeException("Error while inflating XML. " +
                            "`name` attribute is required")
            a.recycle()
        }
    }
}

Usage

my_navigation.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/navigation"
    app:startDestination="@id/navigation_home">

    <fragment
        android:id="@+id/navigation_assistant"
        android:name="com.example.ui.HomeFragment"
        tools:layout="@layout/home">
        <action
            android:id="@+id/action_nav_to_dialog"
            app:destination="@id/navigation_dialog" />
    </fragment>

    <dialog-fragment
        android:id="@+id/navigation_dialog"
        android:name="com.example.ui.MyDialogFragment"
        tools:layout="@layout/my_dialog" />

</navigation>    

The fragment that will navigate.

class HomeFragment : Fragment(), NavHost {

    private val navControllerInternal: NavController by lazy(LazyThreadSafetyMode.NONE){
        NavController(context!!)
    }

    override fun getNavController(): NavController = navControllerInternal

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Built-in navigator for `fragment` XML tag
        navControllerInternal.navigatorProvider.addNavigator(
            FragmentNavigator(context!!, childFragmentManager, this.id)
        )
        // Your custom navigator for `dialog-fragment` XML tag
        navControllerInternal.navigatorProvider.addNavigator(
            DialogFragmentNavigator(context!!, childFragmentManager)
        )
        navControllerInternal.setGraph(R.navigation.my_navigation)
    }

    override fun onCreateView(inflater: LayoutInflater, 
                              container: ViewGroup?, savedInstanceState: Bundle?): View? {
        super.onCreateView(inflater, container, savedInstanceState)
        val view = inflater.inflate(R.layout.home)
        view.id = this.id

        view.button.setOnClickListener{
            getNavController().navigate(R.id.action_nav_to_dialog)
        }

        return view
    }
}
Allan Veloso
  • 5,823
  • 1
  • 38
  • 36
3

I created custom navigator for DialogFragment.

Sample is here.
(It's just sample, so it might be any problem.)

@Navigator.Name("dialog_fragment")
class DialogNavigator(
    private val fragmentManager: FragmentManager
) : Navigator<DialogNavigator.Destination>() {

    companion object {
        private const val TAG = "dialog"
    }

    override fun navigate(destination: Destination, args: Bundle?, 
            navOptions: NavOptions?, navigatorExtras: Extras?) {
        val fragment = destination.createFragment(args)
       fragment.setTargetFragment(fragmentManager.primaryNavigationFragment, 
               SimpleDialogArgs.fromBundle(args).requestCode)
        fragment.show(fragmentManager, TAG)
        dispatchOnNavigatorNavigated(destination.id, BACK_STACK_UNCHANGED)
    }

    override fun createDestination(): Destination {
        return Destination(this)
    }

    override fun popBackStack(): Boolean {
        return true
    }

    class Destination(
            navigator: Navigator<out NavDestination>
    ) : NavDestination(navigator) {

        private var fragmentClass: Class<out DialogFragment>? = null

        override fun onInflate(context: Context, attrs: AttributeSet) {
            super.onInflate(context, attrs)
            val a = context.resources.obtainAttributes(attrs,
                    R.styleable.FragmentNavigator)
            a.getString(R.styleable.FragmentNavigator_android_name)
                    ?.let { className ->
                fragmentClass = parseClassFromName(context, className, 
                        DialogFragment::class.java)
            }
            a.recycle()
        }

        fun createFragment(args: Bundle?): DialogFragment {
            val fragment = fragmentClass?.newInstance()
                ?: throw IllegalStateException("fragment class not set")
            args?.let {
                fragment.arguments = it
            }
            return fragment
        }
    }
}
Community
  • 1
  • 1
STAR_ZERO
  • 1,400
  • 11
  • 21
1

Version 2.1.0-alpha03 was Released so we can finally use DialogFragments. Unfortunately for me, I have some issues with the backstack when using cancelable dialogs. Probably I have a faulty implementation of my dialogs..

[LATER-EDIT] My implementation was good, the problem is related to Wrong dialog counting for DialogFragmentNavigator as is described in the issue tracker As a workaround you can have a look on my recommendation

Liviu
  • 768
  • 8
  • 9
  • I ran into this problem as well. Just a single test would have been enough to find this issue, yet they released a new version of the library that doesn't even work because they couldn't bother to test it. Makes you wonder what their quality process is like. – Julio E. Rodríguez Cabañas May 14 '19 at 09:49
0

Updated for:

implementation "androidx.navigation:navigation-ui-ktx:2.2.0-rc04"

And use in my_nav_graph.xml

<dialog
android:id="@+id/my_dialog"
android:name="com.example.ui.MyDialogFragment"
tools:layout="@layout/my_dialog" />
Razor
  • 447
  • 5
  • 10
0

Yes. It's possible in the latest update of Navigation Component. You can check this link to have a clear concept. raywenderlich.com

Aminul Haque Aome
  • 2,261
  • 21
  • 34
-1

One option would be to just use a regular fragment and make it look similar to a dialog. I found it was not worth the hassle so I used the standard way using show(). If you insist See here for a way of doing it.

Jeffrey
  • 1,998
  • 1
  • 25
  • 22
-1

Yes, It is possible now. In it's initial release it wasn't possible but now from "androidx.navigation:navigation-fragment:2.1.0-alpha03" this navigation version you can use dialog fragment in navigation component.

Check this out:- Naviagtion dialog fragment support

Community
  • 1
  • 1
Deepak Rajput
  • 731
  • 6
  • 20