Search code examples
androidandroid-fragmentsandroid-navigationandroid-navigationview

Android Navigation Component not working with Dialog Fragments


Disclaimer: I've checked the documentation and since 2.1.0 the navigation components has supported Dialog Fragments. (https://developer.android.com/jetpack/androidx/releases/navigation#2.1.0)

Error That I'm Getting

I'm getting this error when trying to go from a DialogFragment to my Start Destination:

java.lang.IllegalStateException: Fragment PostDistressDialog{829f5d1} (bbbc4926-684b-491b-9772-e0f0ffebe0af)} not associated with a fragment manager.

PostDistressDialog is a DialogFragment called from JournalEntryFragment(can be seen in map below) using the navigation component. PostDistressDialog is not an inner class of JournalEntryFragment. It is in a class of its own extending DialogFragment

Picture of my Navigation Graph

enter image description here

Function Calling NavController

public class PostDistressDialog extends DialogFragment implements ISaveDatabase {

...

@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
    if (getArguments()!=null) {

        ...

        // Set up the Alert Dialog
        AlertDialog.Builder alertDialog = new AlertDialog.Builder(getContext());
        alertDialog.setTitle(R.string.distressed_levels);
        alertDialog.setMessage(R.string.distressed_how_feel_post);

        // Inflate and set the layout for the dialog
        View layout = View.inflate(getActivity(), R.layout.dialog_seekbar, null);
        alertDialog.setView(layout);
        
        ....
        // Add okay button
        alertDialog.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
                // Save post distress value in Journal Entry
                mJournalEntry.setPostDistress(mTempDistressValue);

                // Save to Journal Entry to database
                // Check if journal entry empty
                if(isJournalEntryEmpty(mJournalEntry)){
                   ...
                }
                else{
                    // Give title if empty
                    if(mJournalEntry.getTitle().isEmpty()) {
                        ....
                    // Save to database
                    new SaveDatabase(getContext(),PostDistressDialog.this).execute(mJournalEntry);
                }

                // Go to main menu
            }
        });

        return alertDialog.create();
    }

    return null;
}

...

@Override
public void databaseSavingCompleted(){
    NavHostFragment.findNavController(this).navigate(PostDistressDialogDirections.postDistressDialogToJournalListAction());
}

}

Where this is public class PostDistressDialog extends DialogFragment

Dialog in my Navigation XML File

<dialog
    android:id="@+id/postDistressDialog"
    android:name="com.dgrullon.cbtjourney.dialogs.PostDistressDialog"
    android:label="PostDistressDialog" >
    <argument
        android:name="postDistressDialogArguments"
        app:argType="com.dgrullon.cbtjourney.pojo.JournalEntries"/>
    <action
        android:id="@+id/postDistressDialog_to_journalListAction"
        app:destination="@id/journalList"
        app:popUpTo="@id/journalList"
        app:popUpToInclusive="true" />
</dialog>

Solution

  • AlertDialog automatically dismisses the Dialog (and hence, removes your DialogFragment) when the callback you add to setPositiveButton is fired. Because you're doing work asynchronously, your databaseSavingCompleted method is called after the DialogFragment is destroyed, detached from the FragmentManager, and removed from the NavController - you're leaking a reference to your DialogFragment (as it would otherwise be garbage collected).

    Therefore when NavHostFragment.findNavController(this) fires, all hooks that would let it access the NavController are already cleaned up.

    If you don't want your button to immediately dismiss the dialog, you need to pass in null to setPositiveButton() and instead get a reference to the button after the dialog has been created by calling its getButton() API and manually setting an OnClickListener that would kick off your AsyncTask (and disable the button to prevent it from being clicked more than once).