public final class

DialogFragmentNavigator

extends Navigator<DialogFragmentNavigator.Destination>

 java.lang.Object

androidx.navigation.Navigator<DialogFragmentNavigator.Destination>

↳androidx.navigation.fragment.DialogFragmentNavigator

Overview

Navigator that uses DialogFragment.show(FragmentManager, String). Every destination using this Navigator must set a valid DialogFragment class name with android:name or DialogFragmentNavigator.Destination.setClassName(String).

Summary

Constructors
publicDialogFragmentNavigator(Context context, FragmentManager manager)

Methods
public abstract NavDestinationcreateDestination()

Construct a new NavDestination associated with this Navigator.

public abstract NavDestinationnavigate(NavDestination destination, Bundle args, NavOptions navOptions, Navigator.Extras navigatorExtras)

Navigate to a destination.

public voidonRestoreState(Bundle savedState)

Restore any state previously saved in Navigator.onSaveState().

public BundleonSaveState()

Called to ask for a representing the Navigator's state.

public abstract booleanpopBackStack()

Attempt to pop this navigator's back stack, performing the appropriate navigation.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public DialogFragmentNavigator(Context context, FragmentManager manager)

Methods

public abstract boolean popBackStack()

Attempt to pop this navigator's back stack, performing the appropriate navigation.

Implementations should return true if navigation was successful. Implementations should return false if navigation could not be performed, for example if the navigator's back stack was empty.

Returns:

true if pop was successful

public abstract NavDestination createDestination()

Construct a new NavDestination associated with this Navigator.

Any initialization of the destination should be done in the destination's constructor as it is not guaranteed that every destination will be created through this method.

Returns:

a new NavDestination

public abstract NavDestination navigate(NavDestination destination, Bundle args, NavOptions navOptions, Navigator.Extras navigatorExtras)

Navigate to a destination.

Requests navigation to a given destination associated with this navigator in the navigation graph. This method generally should not be called directly; NavController will delegate to it when appropriate.

Parameters:

destination: destination node to navigate to
args: arguments to use for navigation
navOptions: additional options for navigation
navigatorExtras: extras unique to your Navigator.

Returns:

The NavDestination that should be added to the back stack or null if no change was made to the back stack (i.e., in cases of single top operations where the destination is already on top of the back stack).

public Bundle onSaveState()

Called to ask for a representing the Navigator's state. This will be restored in Navigator.onRestoreState(Bundle).

public void onRestoreState(Bundle savedState)

Restore any state previously saved in Navigator.onSaveState(). This will be called before any calls to Navigator.navigate(D, Bundle, NavOptions, Navigator.Extras) or Navigator.popBackStack().

Calls to Navigator.createDestination() should not be dependent on any state restored here as Navigator.createDestination() can be called before the state is restored.

Parameters:

savedState: The state previously saved

Source

/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.navigation.fragment;

import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;

import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.navigation.FloatingWindow;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
import androidx.navigation.NavOptions;
import androidx.navigation.Navigator;
import androidx.navigation.NavigatorProvider;

import java.util.HashSet;

/**
 * Navigator that uses {@link DialogFragment#show(FragmentManager, String)}. Every
 * destination using this Navigator must set a valid DialogFragment class name with
 * <code>android:name</code> or {@link Destination#setClassName(String)}.
 */
@Navigator.Name("dialog")
public final class DialogFragmentNavigator extends Navigator<DialogFragmentNavigator.Destination> {
    private static final String TAG = "DialogFragmentNavigator";
    private static final String KEY_DIALOG_COUNT = "androidx-nav-dialogfragment:navigator:count";
    private static final String DIALOG_TAG = "androidx-nav-fragment:navigator:dialog:";

    private final Context mContext;
    private final FragmentManager mFragmentManager;
    private int mDialogCount = 0;
    private final HashSet<String> mRestoredTagsAwaitingAttach = new HashSet<>();

    private LifecycleEventObserver mObserver = new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_STOP) {
                DialogFragment dialogFragment = (DialogFragment) source;
                if (!dialogFragment.requireDialog().isShowing()) {
                    NavHostFragment.findNavController(dialogFragment).popBackStack();
                }
            }
        }
    };

    public DialogFragmentNavigator(@NonNull Context context, @NonNull FragmentManager manager) {
        mContext = context;
        mFragmentManager = manager;
    }

    @Override
    public boolean popBackStack() {
        if (mDialogCount == 0) {
            return false;
        }
        if (mFragmentManager.isStateSaved()) {
            Log.i(TAG, "Ignoring popBackStack() call: FragmentManager has already"
                    + " saved its state");
            return false;
        }
        Fragment existingFragment = mFragmentManager
                .findFragmentByTag(DIALOG_TAG + --mDialogCount);
        if (existingFragment != null) {
            existingFragment.getLifecycle().removeObserver(mObserver);
            ((DialogFragment) existingFragment).dismiss();
        }
        return true;
    }

    @NonNull
    @Override
    public Destination createDestination() {
        return new Destination(this);
    }

    @Nullable
    @Override
    public NavDestination navigate(@NonNull final Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        if (mFragmentManager.isStateSaved()) {
            Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
                    + " saved its state");
            return null;
        }
        String className = destination.getClassName();
        if (className.charAt(0) == '.') {
            className = mContext.getPackageName() + className;
        }
        final Fragment frag = mFragmentManager.getFragmentFactory().instantiate(
                mContext.getClassLoader(), className);
        if (!DialogFragment.class.isAssignableFrom(frag.getClass())) {
            throw new IllegalArgumentException("Dialog destination " + destination.getClassName()
                    + " is not an instance of DialogFragment");
        }
        final DialogFragment dialogFragment = (DialogFragment) frag;
        dialogFragment.setArguments(args);
        dialogFragment.getLifecycle().addObserver(mObserver);

        dialogFragment.show(mFragmentManager, DIALOG_TAG + mDialogCount++);

        return destination;
    }

    @Override
    @Nullable
    public Bundle onSaveState() {
        if (mDialogCount == 0) {
            return null;
        }
        Bundle b = new Bundle();
        b.putInt(KEY_DIALOG_COUNT, mDialogCount);
        return b;
    }

    @Override
    public void onRestoreState(@Nullable Bundle savedState) {
        if (savedState != null) {
            mDialogCount = savedState.getInt(KEY_DIALOG_COUNT, 0);
            for (int index = 0; index < mDialogCount; index++) {
                DialogFragment fragment = (DialogFragment) mFragmentManager
                        .findFragmentByTag(DIALOG_TAG + index);
                if (fragment != null) {
                    fragment.getLifecycle().addObserver(mObserver);
                } else {
                    mRestoredTagsAwaitingAttach.add(DIALOG_TAG + index);
                }
            }
        }
    }

    // TODO: Switch to FragmentOnAttachListener once we depend on Fragment 1.3
    void onAttachFragment(@NonNull Fragment childFragment) {
        boolean needToAddObserver = mRestoredTagsAwaitingAttach.remove(childFragment.getTag());
        if (needToAddObserver) {
            childFragment.getLifecycle().addObserver(mObserver);
        }
    }

    /**
     * NavDestination specific to {@link DialogFragmentNavigator}.
     */
    @NavDestination.ClassType(DialogFragment.class)
    public static class Destination extends NavDestination implements FloatingWindow {

        private String mClassName;

        /**
         * Construct a new fragment destination. This destination is not valid until you set the
         * Fragment via {@link #setClassName(String)}.
         *
         * @param navigatorProvider The {@link NavController} which this destination
         *                          will be associated with.
         */
        public Destination(@NonNull NavigatorProvider navigatorProvider) {
            this(navigatorProvider.getNavigator(DialogFragmentNavigator.class));
        }

        /**
         * Construct a new fragment destination. This destination is not valid until you set the
         * Fragment via {@link #setClassName(String)}.
         *
         * @param fragmentNavigator The {@link DialogFragmentNavigator} which this destination
         *                          will be associated with. Generally retrieved via a
         *                          {@link NavController}'s
         *                          {@link NavigatorProvider#getNavigator(Class)} method.
         */
        public Destination(@NonNull Navigator<? extends Destination> fragmentNavigator) {
            super(fragmentNavigator);
        }

        @CallSuper
        @Override
        public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
            super.onInflate(context, attrs);
            TypedArray a = context.getResources().obtainAttributes(attrs,
                    R.styleable.DialogFragmentNavigator);
            String className = a.getString(R.styleable.DialogFragmentNavigator_android_name);
            if (className != null) {
                setClassName(className);
            }
            a.recycle();
        }

        /**
         * Set the DialogFragment class name associated with this destination
         * @param className The class name of the DialogFragment to show when you navigate to this
         *                  destination
         * @return this {@link Destination}
         */
        @NonNull
        public final Destination setClassName(@NonNull String className) {
            mClassName = className;
            return this;
        }

        /**
         * Gets the DialogFragment's class name associated with this destination
         *
         * @throws IllegalStateException when no DialogFragment class was set.
         */
        @NonNull
        public final String getClassName() {
            if (mClassName == null) {
                throw new IllegalStateException("DialogFragment class was not set");
            }
            return mClassName;
        }
    }
}