/* * Copyright 2018 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.fragment.app; import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; import static androidx.fragment.app.FragmentManager.TAG; import android.annotation.SuppressLint; import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import androidx.activity.ComponentDialog; import androidx.annotation.CallSuper; import androidx.annotation.IntDef; import androidx.annotation.LayoutRes; import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; import androidx.annotation.StyleRes; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewTreeLifecycleOwner; import androidx.lifecycle.ViewTreeViewModelStoreOwner; import androidx.savedstate.ViewTreeSavedStateRegistryOwner; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * A fragment that displays a dialog window, floating in the foreground of its * activity's window. This fragment contains a Dialog object, which it * displays as appropriate based on the fragment's state. Control of * the dialog (deciding when to show, hide, dismiss it) should be done through * the APIs here, not with direct calls on the dialog. * *

Implementations should override this class and implement * {@link #onViewCreated(View, Bundle)} to supply the * content of the dialog. Alternatively, they can override * {@link #onCreateDialog(Bundle)} to create an entirely custom dialog, such * as an AlertDialog, with its own content. * *

Topics covered here: *

    *
  1. Lifecycle *
  2. Basic Dialog *
  3. Alert Dialog *
  4. Selecting Between Dialog or Embedding *
* * *

Lifecycle

* *

DialogFragment does various things to keep the fragment's lifecycle * driving it, instead of the Dialog. Note that dialogs are generally * autonomous entities -- they are their own window, receiving their own * input events, and often deciding on their own when to disappear (by * receiving a back key event or the user clicking on a button). * *

DialogFragment needs to ensure that what is happening with the Fragment * and Dialog states remains consistent. To do this, it watches for dismiss * events from the dialog and takes care of removing its own state when they * happen. This means you should use {@link #show(FragmentManager, String)}, * {@link #show(FragmentTransaction, String)}, or {@link #showNow(FragmentManager, String)} * to add an instance of DialogFragment to your UI, as these keep track of * how DialogFragment should remove itself when the dialog is dismissed. * * *

Basic Dialog

* *

The simplest use of DialogFragment is as a floating container for the * fragment's view hierarchy. A simple implementation may look like this: * *

{@code
 * public class MyDialogFragment extends DialogFragment {
 *     int mNum;
 *
 *     // Create a new instance of MyDialogFragment, providing "num" as an argument.
 *     static MyDialogFragment newInstance(int num) {
 *         MyDialogFragment f = new MyDialogFragment();
 *
 *         // Supply num input as an argument.
 *         Bundle args = new Bundle();
 *         args.putInt("num", num);
 *         f.setArguments(args);
 *
 *         return f;
 *     }
 *
 *     {@literal @}Override
 *     public void onCreate(Bundle savedInstanceState) {
 *         super.onCreate(savedInstanceState);
 *         mNum = getArguments().getInt("num");
 *
 *         // Pick a style based on the num.
 *         int style = DialogFragment.STYLE_NORMAL, theme = 0;
 *         switch ((mNum-1)%6) {
 *             case 1: style = DialogFragment.STYLE_NO_TITLE; break;
 *             case 2: style = DialogFragment.STYLE_NO_FRAME; break;
 *             case 3: style = DialogFragment.STYLE_NO_INPUT; break;
 *             case 4: style = DialogFragment.STYLE_NORMAL; break;
 *             case 5: style = DialogFragment.STYLE_NORMAL; break;
 *             case 6: style = DialogFragment.STYLE_NO_TITLE; break;
 *             case 7: style = DialogFragment.STYLE_NO_FRAME; break;
 *             case 8: style = DialogFragment.STYLE_NORMAL; break;
 *         }
 *         switch ((mNum-1)%6) {
 *             case 4: theme = android.R.style.Theme_Holo; break;
 *             case 5: theme = android.R.style.Theme_Holo_Light_Dialog; break;
 *             case 6: theme = android.R.style.Theme_Holo_Light; break;
 *             case 7: theme = android.R.style.Theme_Holo_Light_Panel; break;
 *             case 8: theme = android.R.style.Theme_Holo_Light; break;
 *         }
 *         setStyle(style, theme);
 *     }
 *
 *     {@literal @}Override
 *     public View onCreateView(LayoutInflater inflater, ViewGroup container,
 *                              Bundle savedInstanceState) {
 *         return inflater.inflate(R.layout.fragment_dialog, container, false);
 *     }
 *
 *     {@literal @}Override
 *     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
 *         super.onViewCreated(view, savedInstanceState);
 *
 *         // set DialogFragment title
 *         getDialog().setTitle("Dialog #" + mNum);
 *     }
 * }
 * }
* *

An example showDialog() method on the Activity could be: * *

{@code
 * public void showDialog() {
 *     mStackLevel++;
 *
 *     // DialogFragment.show() will take care of adding the fragment
 *     // in a transaction.  We also want to remove any currently showing
 *     // dialog, so make our own transaction and take care of that here.
 *     FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 *     Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog");
 *     if (prev != null) {
 *         ft.remove(prev);
 *     }
 *     ft.addToBackStack(null);
 *
 *     // Create and show the dialog.
 *     DialogFragment newFragment = MyDialogFragment.newInstance(mStackLevel);
 *     newFragment.show(ft, "dialog");
 * }
 * }
* *

This removes any currently shown dialog, creates a new DialogFragment * with an argument, and shows it as a new state on the back stack. When the * transaction is popped, the current DialogFragment and its Dialog will be * destroyed, and the previous one (if any) re-shown. Note that in this case * DialogFragment will take care of popping the transaction of the Dialog that * is dismissed separately from it. * * *

Alert Dialog

* *

Instead of (or in addition to) implementing {@link #onViewCreated(View, Bundle)} to * generate the view hierarchy inside of a dialog, you may implement * {@link #onCreateDialog(Bundle)} to create your own custom Dialog object. * *

This is most useful for creating an AlertDialog, allowing you * to display standard alerts to the user that are managed by a fragment. * A simple example implementation of this is: * *

{@code
 * public static class MyAlertDialogFragment extends DialogFragment {
 *
 *     public static MyAlertDialogFragment newInstance(int title) {
 *         MyAlertDialogFragment frag = new MyAlertDialogFragment();
 *         Bundle args = new Bundle();
 *         args.putInt("title", title);
 *         frag.setArguments(args);
 *         return frag;
 *     }
 *
 *     {@literal @}Override
 *     public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
 *
 *         return new AlertDialog.Builder(getActivity())
 *                 .setIcon(R.drawable.alert_dialog_icon)
 *                 .setTitle(title)
 *                 .setPositiveButton(R.string.alert_dialog_ok,
 *                         (dialogInterface, i) -> ((MainActivity)getActivity()).doPositiveClick())
 *                 .setNegativeButton(R.string.alert_dialog_cancel,
 *                         (dialogInterface, i) -> ((MainActivity)getActivity()).doNegativeClick())
 *                 .create();
 *         return super.onCreateDialog(savedInstanceState);
 *     }
 * }
 * }
* *

The activity creating this fragment may have the following methods to * show the dialog and receive results from it: * *

{@code
 * void showDialog() {
 *     DialogFragment newFragment = MyAlertDialogFragment.newInstance(
 *             R.string.alert_dialog_two_buttons_title);
 *     newFragment.show(getSupportFragmentManager(), "dialog");
 * }
 *
 * public void doPositiveClick() {
 *     // Do stuff here.
 *     Log.i("MainActivity", "Positive click!");
 * }
 *
 * public void doNegativeClick() {
 *     // Do stuff here.
 *     Log.i("MainActivity", "Negative click!");
 * }
 * }
* *

Note that in this case the fragment is not placed on the back stack, it * is just added as an indefinitely running fragment. Because dialogs normally * are modal, this will still operate as a back stack, since the dialog will * capture user input until it is dismissed. When it is dismissed, DialogFragment * will take care of removing itself from its fragment manager. * * *

Selecting Between Dialog or Embedding

* *

A DialogFragment can still optionally be used as a normal fragment, if * desired. This is useful if you have a fragment that in some cases should * be shown as a dialog and others embedded in a larger UI. This behavior * will normally be automatically selected for you based on how you are using * the fragment, but can be customized with {@link #setShowsDialog(boolean)}. * *

For example, here is a simple dialog fragment: * *

{@code
 * public static class MyDialogFragment extends DialogFragment {
 *     static MyDialogFragment newInstance() {
 *         return new MyDialogFragment();
 *     }
 *
 *     {@literal @}Override
 *     public void onCreate(Bundle savedInstanceState) {
 *         super.onCreate(savedInstanceState);
 *
 *         // this fragment will be displayed in a dialog
 *         setShowsDialog(true);
 *     }
 *
 *     {@literal @}Override
 *     public View onCreateView(LayoutInflater inflater, ViewGroup container,
 *             Bundle savedInstanceState) {
 *         View v = inflater.inflate(R.layout.hello_world, container, false);
 *         View tv = v.findViewById(R.id.text);
 *         ((TextView)tv).setText("This is an instance of MyDialogFragment");
 *         return v;
 *     }
 * }
 * }
* *

An instance of this fragment can be created and shown as a dialog: * *

{@code
 * void showDialog() {
 *     // Create the fragment and show it as a dialog.
 *     DialogFragment newFragment = MyDialogFragment.newInstance();
 *     newFragment.show(getSupportFragmentManager(), "dialog");
 * }
 * }
* *

It can also be added as content in a view hierarchy: * *

{@code
 * FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 * DialogFragment newFragment = MyDialogFragment.newInstance();
 * ft.add(R.id.embedded, newFragment);
 * ft.commit();
 * }
*/ public class DialogFragment extends Fragment implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { @RestrictTo(LIBRARY_GROUP_PREFIX) @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT}) @Retention(RetentionPolicy.SOURCE) private @interface DialogStyle {} /** * Style for {@link #setStyle(int, int)}: a basic, * normal dialog. */ public static final int STYLE_NORMAL = 0; /** * Style for {@link #setStyle(int, int)}: don't include * a title area. */ public static final int STYLE_NO_TITLE = 1; /** * Style for {@link #setStyle(int, int)}: don't draw * any frame at all; the view hierarchy returned by {@link #onCreateView} * is entirely responsible for drawing the dialog. */ public static final int STYLE_NO_FRAME = 2; /** * Style for {@link #setStyle(int, int)}: like * {@link #STYLE_NO_FRAME}, but also disables all input to the dialog. * The user can not touch it, and its window will not receive input focus. */ public static final int STYLE_NO_INPUT = 3; private static final String SAVED_DIALOG_STATE_TAG = "android:savedDialogState"; private static final String SAVED_STYLE = "android:style"; private static final String SAVED_THEME = "android:theme"; private static final String SAVED_CANCELABLE = "android:cancelable"; private static final String SAVED_SHOWS_DIALOG = "android:showsDialog"; private static final String SAVED_BACK_STACK_ID = "android:backStackId"; /** * Copied from {@link Dialog}. */ private static final String SAVED_INTERNAL_DIALOG_SHOWING = "android:dialogShowing"; private Handler mHandler; private Runnable mDismissRunnable = new Runnable() { @SuppressLint("SyntheticAccessor") @Override public void run() { mOnDismissListener.onDismiss(mDialog); } }; private DialogInterface.OnCancelListener mOnCancelListener = new DialogInterface.OnCancelListener() { @SuppressLint("SyntheticAccessor") @Override public void onCancel(@Nullable DialogInterface dialog) { if (mDialog != null) { DialogFragment.this.onCancel(mDialog); } } }; private DialogInterface.OnDismissListener mOnDismissListener = new DialogInterface.OnDismissListener() { @SuppressLint("SyntheticAccessor") @Override public void onDismiss(@Nullable DialogInterface dialog) { if (mDialog != null) { DialogFragment.this.onDismiss(mDialog); } } }; private int mStyle = STYLE_NORMAL; private int mTheme = 0; private boolean mCancelable = true; private boolean mShowsDialog = true; private int mBackStackId = -1; private boolean mCreatingDialog; private Observer mObserver = new Observer() { @SuppressLint("SyntheticAccessor") @Override public void onChanged(LifecycleOwner lifecycleOwner) { if (lifecycleOwner != null && mShowsDialog) { View view = requireView(); if (view.getParent() != null) { throw new IllegalStateException( "DialogFragment can not be attached to a container view"); } if (mDialog != null) { if (FragmentManager.isLoggingEnabled(Log.DEBUG)) { Log.d(TAG, "DialogFragment " + this + " setting the content view on " + mDialog); } mDialog.setContentView(view); } } } }; @Nullable private Dialog mDialog; private boolean mViewDestroyed; private boolean mDismissed; private boolean mShownByMe; private boolean mDialogCreated = false; /** * Constructor used by the default {@link FragmentFactory}. You must * {@link FragmentManager#setFragmentFactory(FragmentFactory) set a custom FragmentFactory} * if you want to use a non-default constructor to ensure that your constructor * is called when the fragment is re-instantiated. * *

It is strongly recommended to supply arguments with {@link #setArguments} * and later retrieved by the Fragment with {@link #getArguments}. These arguments * are automatically saved and restored alongside the Fragment. * *

Applications should generally not implement a constructor. Prefer * {@link #onAttach(Context)} instead. It is the first place application code can run where * the fragment is ready to be used - the point where the fragment is actually associated with * its context. */ public DialogFragment() { super(); } /** * Alternate constructor that can be called from your default, no argument constructor to * provide a default layout that will be inflated by * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}. * *

     * class MyDialogFragment extends DialogFragment {
     *   public MyDialogFragment() {
     *     super(R.layout.dialog_fragment_main);
     *   }
     * }
     * 
* * You must * {@link FragmentManager#setFragmentFactory(FragmentFactory) set a custom FragmentFactory} * if you want to use a non-default constructor to ensure that your constructor is called * when the fragment is re-instantiated. * * @see #DialogFragment() * @see #onCreateView(LayoutInflater, ViewGroup, Bundle) */ public DialogFragment(@LayoutRes int contentLayoutId) { super(contentLayoutId); } /** * Call to customize the basic appearance and behavior of the * fragment's dialog. This can be used for some common dialog behaviors, * taking care of selecting flags, theme, and other options for you. The * same effect can be achieve by manually setting Dialog and Window * attributes yourself. Calling this after the fragment's Dialog is * created will have no effect. * * @param style Selects a standard style: may be {@link #STYLE_NORMAL}, * {@link #STYLE_NO_TITLE}, {@link #STYLE_NO_FRAME}, or * {@link #STYLE_NO_INPUT}. * @param theme Optional custom theme. If 0, an appropriate theme (based * on the style) will be selected for you. */ public void setStyle(@DialogStyle int style, @StyleRes int theme) { if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) { Log.d(TAG, "Setting style and theme for DialogFragment " + this + " to " + style + ", " + theme); } mStyle = style; if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) { mTheme = android.R.style.Theme_Panel; } if (theme != 0) { mTheme = theme; } } /** * Display the dialog, adding the fragment to the given FragmentManager. This * is a convenience for explicitly creating a transaction, adding the * fragment to it with the given tag, and {@link FragmentTransaction#commit() committing} it. * This does not add the transaction to the fragment back stack. When the fragment * is dismissed, a new transaction will be executed to remove it from * the activity. * @param manager The FragmentManager this fragment will be added to. * @param tag The tag for this fragment, as per * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. */ public void show(@NonNull FragmentManager manager, @Nullable String tag) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); ft.setReorderingAllowed(true); ft.add(this, tag); ft.commit(); } /** * Display the dialog, adding the fragment using an existing transaction * and then {@link FragmentTransaction#commit() committing} the transaction. * @param transaction An existing transaction in which to add the fragment. * @param tag The tag for this fragment, as per * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. * @return Returns the identifier of the committed transaction, as per * {@link FragmentTransaction#commit() FragmentTransaction.commit()}. */ public int show(@NonNull FragmentTransaction transaction, @Nullable String tag) { mDismissed = false; mShownByMe = true; transaction.add(this, tag); mViewDestroyed = false; mBackStackId = transaction.commit(); return mBackStackId; } /** * Display the dialog, immediately adding the fragment to the given FragmentManager. This * is a convenience for explicitly creating a transaction, adding the * fragment to it with the given tag, and calling {@link FragmentTransaction#commitNow()}. * This does not add the transaction to the fragment back stack. When the fragment * is dismissed, a new transaction will be executed to remove it from * the activity. * @param manager The FragmentManager this fragment will be added to. * @param tag The tag for this fragment, as per * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. */ public void showNow(@NonNull FragmentManager manager, @Nullable String tag) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); ft.setReorderingAllowed(true); ft.add(this, tag); ft.commitNow(); } /** * Dismiss the fragment and its dialog. If the fragment was added to the * back stack, all back stack state up to and including this entry will * be popped. Otherwise, a new transaction will be committed to remove * the fragment. */ public void dismiss() { dismissInternal(false, false, false); } /** * Version of {@link #dismiss()} that uses {@link FragmentTransaction#commitNow()}. * See linked documentation for further details. */ @MainThread public void dismissNow() { dismissInternal(false, false, true); } /** * Version of {@link #dismiss()} that uses * {@link FragmentTransaction#commitAllowingStateLoss() * FragmentTransaction.commitAllowingStateLoss()}. See linked * documentation for further details. */ public void dismissAllowingStateLoss() { dismissInternal(true, false, false); } private void dismissInternal(boolean allowStateLoss, boolean fromOnDismiss, boolean immediate) { if (mDismissed) { return; } mDismissed = true; mShownByMe = false; if (mDialog != null) { // Instead of waiting for a posted onDismiss(), null out // the listener and call onDismiss() manually to ensure // that the callback happens before onDestroy() mDialog.setOnDismissListener(null); mDialog.dismiss(); if (!fromOnDismiss) { // onDismiss() is always called on the main thread, so // we mimic that behavior here. The difference here is that // we don't post the message to ensure that the onDismiss() // callback still happens before onDestroy() if (Looper.myLooper() == mHandler.getLooper()) { onDismiss(mDialog); } else { mHandler.post(mDismissRunnable); } } } mViewDestroyed = true; if (mBackStackId >= 0) { if (immediate) { getParentFragmentManager().popBackStackImmediate(mBackStackId, FragmentManager.POP_BACK_STACK_INCLUSIVE); } else { getParentFragmentManager().popBackStack(mBackStackId, FragmentManager.POP_BACK_STACK_INCLUSIVE, allowStateLoss); } mBackStackId = -1; } else { FragmentTransaction ft = getParentFragmentManager().beginTransaction(); ft.setReorderingAllowed(true); ft.remove(this); // allowStateLoss and immediate should not both be true if (immediate) { ft.commitNow(); } else if (allowStateLoss) { ft.commitAllowingStateLoss(); } else { ft.commit(); } } } /** * Return the {@link Dialog} this fragment is currently controlling. * * @see #requireDialog() */ @Nullable public Dialog getDialog() { return mDialog; } /** * Return the {@link Dialog} this fragment is currently controlling. * * @throws IllegalStateException if the Dialog has not yet been created (before * {@link #onCreateDialog(Bundle)}) or has been destroyed (after {@link #onDestroyView()}. * @see #getDialog() */ @NonNull public final Dialog requireDialog() { Dialog dialog = getDialog(); if (dialog == null) { throw new IllegalStateException("DialogFragment " + this + " does not have a Dialog."); } return dialog; } @StyleRes public int getTheme() { return mTheme; } /** * Control whether the shown Dialog is cancelable. Use this instead of * directly calling {@link Dialog#setCancelable(boolean) * Dialog.setCancelable(boolean)}, because DialogFragment needs to change * its behavior based on this. * * @param cancelable If true, the dialog is cancelable. The default * is true. */ public void setCancelable(boolean cancelable) { mCancelable = cancelable; if (mDialog != null) mDialog.setCancelable(cancelable); } /** * Return the current value of {@link #setCancelable(boolean)}. */ public boolean isCancelable() { return mCancelable; } /** * Controls whether this fragment should be shown in a dialog. If not * set, no Dialog will be created and the fragment's view hierarchy will * thus not be added to it. This allows you to instead use it as a * normal fragment (embedded inside of its activity). * *

This is normally set for you based on whether the fragment is * associated with a container view ID passed to * {@link FragmentTransaction#add(int, Fragment) FragmentTransaction.add(int, Fragment)}. * If the fragment was added with a container, setShowsDialog will be * initialized to false; otherwise, it will be true. * *

If calling this manually, it should be called in {@link #onCreate(Bundle)} * as calling it any later will have no effect. * * @param showsDialog If true, the fragment will be displayed in a Dialog. * If false, no Dialog will be created and the fragment's view hierarchy * left undisturbed. */ public void setShowsDialog(boolean showsDialog) { mShowsDialog = showsDialog; } /** * Return the current value of {@link #setShowsDialog(boolean)}. */ public boolean getShowsDialog() { return mShowsDialog; } @MainThread @Override public void onAttach(@NonNull Context context) { super.onAttach(context); getViewLifecycleOwnerLiveData().observeForever(mObserver); if (!mShownByMe) { // If not explicitly shown through our API, take this as an // indication that the dialog is no longer dismissed. mDismissed = false; } } @MainThread @Override public void onDetach() { super.onDetach(); if (!mShownByMe && !mDismissed) { // The fragment was not shown by a direct call here, it is not // dismissed, and now it is being detached... well, okay, thou // art now dismissed. Have fun. mDismissed = true; } getViewLifecycleOwnerLiveData().removeObserver(mObserver); } @MainThread @Override @SuppressWarnings("deprecation") public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // This assumes that onCreate() is being called on the main thread mHandler = new Handler(); mShowsDialog = mContainerId == 0; if (savedInstanceState != null) { mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL); mTheme = savedInstanceState.getInt(SAVED_THEME, 0); mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true); mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog); mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1); } } @Override void performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { super.performCreateView(inflater, container, savedInstanceState); // If no view was set, we need to call onRestoreInstance on the dialog to ensure // the state is restored. if (mView == null) { if (mDialog != null && savedInstanceState != null) { Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG); if (dialogState != null) { mDialog.onRestoreInstanceState(dialogState); } } } } @NonNull @Override FragmentContainer createFragmentContainer() { final FragmentContainer fragmentContainer = super.createFragmentContainer(); return new FragmentContainer() { @Nullable @Override public View onFindViewById(int id) { if (fragmentContainer.onHasView()) { return fragmentContainer.onFindViewById(id); } return DialogFragment.this.onFindViewById(id); } @Override public boolean onHasView() { return fragmentContainer.onHasView() || DialogFragment.this.onHasView(); } }; } @Nullable View onFindViewById(int id) { if (mDialog != null) { return mDialog.findViewById(id); } return null; } boolean onHasView() { return mDialogCreated; } /** * {@inheritDoc} * *

* If this is called from within {@link #onCreateDialog(Bundle)}, the layout inflater from * {@link Fragment#onGetLayoutInflater(Bundle)}, without the dialog theme, will be returned. */ @Override @NonNull public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) { LayoutInflater layoutInflater = super.onGetLayoutInflater(savedInstanceState); if (!mShowsDialog || mCreatingDialog) { if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) { String message = "getting layout inflater for DialogFragment " + this; if (!mShowsDialog) { Log.d(TAG, "mShowsDialog = false: " + message); } else { Log.d(TAG, "mCreatingDialog = true: " + message); } } return layoutInflater; } prepareDialog(savedInstanceState); if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) { Log.d(TAG, "get layout inflater for DialogFragment " + this + " from dialog context"); } if (mDialog != null) { layoutInflater = layoutInflater.cloneInContext(mDialog.getContext()); } return layoutInflater; } @RestrictTo(LIBRARY_GROUP_PREFIX) public void setupDialog(@NonNull Dialog dialog, int style) { switch (style) { case STYLE_NO_INPUT: Window window = dialog.getWindow(); if (window != null) { window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); } // fall through... case STYLE_NO_FRAME: case STYLE_NO_TITLE: dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); } } /** * Override to build your own custom Dialog container. This is typically * used to show an AlertDialog instead of a generic Dialog; when doing so, * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} does not need * to be implemented since the AlertDialog takes care of its own content. * *

This method will be called after {@link #onCreate(Bundle)} and * immediately before {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}. The * default implementation simply instantiates and returns a {@link Dialog} * class. * *

Note: DialogFragment own the {@link Dialog#setOnCancelListener * Dialog.setOnCancelListener} and {@link Dialog#setOnDismissListener * Dialog.setOnDismissListener} callbacks. You must not set them yourself. * To find out about these events, override {@link #onCancel(DialogInterface)} * and {@link #onDismiss(DialogInterface)}.

* * @param savedInstanceState The last saved instance state of the Fragment, * or null if this is a freshly created Fragment. * * @return Return a new Dialog instance to be displayed by the Fragment. */ @MainThread @NonNull public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { if (FragmentManager.isLoggingEnabled(Log.DEBUG)) { Log.d(TAG, "onCreateDialog called for DialogFragment " + this); } return new ComponentDialog(requireContext(), getTheme()); } @Override public void onCancel(@NonNull DialogInterface dialog) { } @CallSuper @Override public void onDismiss(@NonNull DialogInterface dialog) { if (!mViewDestroyed) { // Note: we need to use allowStateLoss, because the dialog // dispatches this asynchronously so we can receive the call // after the activity is paused. Worst case, when the user comes // back to the activity they see the dialog again. if (FragmentManager.isLoggingEnabled(Log.DEBUG)) { Log.d(TAG, "onDismiss called for DialogFragment " + this); } dismissInternal(true, true, false); } } private void prepareDialog(@Nullable Bundle savedInstanceState) { if (!mShowsDialog) { return; } if (!mDialogCreated) { try { mCreatingDialog = true; mDialog = onCreateDialog(savedInstanceState); // mShowsDialog might have changed in onCreateDialog, so we should only proceed // with setting up the dialog if mShowsDialog is still true if (mShowsDialog) { setupDialog(mDialog, mStyle); final Context context = getContext(); if (context instanceof Activity) { mDialog.setOwnerActivity((Activity) context); } mDialog.setCancelable(mCancelable); mDialog.setOnCancelListener(mOnCancelListener); mDialog.setOnDismissListener(mOnDismissListener); mDialogCreated = true; } else { // Ensure that when mShowsDialog is set to false in onCreateDialog // that getDialog() returns null mDialog = null; } } finally { mCreatingDialog = false; } } } @MainThread @Override public void onViewStateRestored(@Nullable Bundle savedInstanceState) { super.onViewStateRestored(savedInstanceState); if (mDialog != null && savedInstanceState != null) { Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG); if (dialogState != null) { mDialog.onRestoreInstanceState(dialogState); } } } /** * {@inheritDoc} * * @deprecated use {@link #onCreateDialog} for code touching the dialog created by * {@link #onCreateDialog}, {@link #onViewCreated(View, Bundle)} for code touching the * view created by {@link #onCreateView} and {@link #onCreate(Bundle)} for other initialization. * To get a callback specifically when a Fragment activity's * {@link Activity#onCreate(Bundle)} is called, register a * {@link androidx.lifecycle.LifecycleObserver} on the Activity's * {@link Lifecycle} in {@link #onAttach(Context)}, removing it when it receives the * {@link Lifecycle.State#CREATED} callback. */ @SuppressWarnings("deprecation") @MainThread @Override @Deprecated public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); } @MainThread @Override public void onStart() { super.onStart(); if (mDialog != null) { mViewDestroyed = false; mDialog.show(); // Only after we show does the dialog window actually return a decor view. View decorView = mDialog.getWindow().getDecorView(); ViewTreeLifecycleOwner.set(decorView, this); ViewTreeViewModelStoreOwner.set(decorView, this); ViewTreeSavedStateRegistryOwner.set(decorView, this); } } @MainThread @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); if (mDialog != null) { Bundle dialogState = mDialog.onSaveInstanceState(); dialogState.putBoolean(SAVED_INTERNAL_DIALOG_SHOWING, false); outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState); } if (mStyle != STYLE_NORMAL) { outState.putInt(SAVED_STYLE, mStyle); } if (mTheme != 0) { outState.putInt(SAVED_THEME, mTheme); } if (!mCancelable) { outState.putBoolean(SAVED_CANCELABLE, mCancelable); } if (!mShowsDialog) { outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog); } if (mBackStackId != -1) { outState.putInt(SAVED_BACK_STACK_ID, mBackStackId); } } @MainThread @Override public void onStop() { super.onStop(); if (mDialog != null) { mDialog.hide(); } } /** * Remove dialog. */ @MainThread @Override public void onDestroyView() { super.onDestroyView(); if (mDialog != null) { // Set removed here because this dismissal is just to hide // the dialog -- we don't want this to cause the fragment to // actually be removed. mViewDestroyed = true; // Instead of waiting for a posted onDismiss(), null out // the listener and call onDismiss() manually to ensure // that the callback happens before onDestroy() mDialog.setOnDismissListener(null); mDialog.dismiss(); if (!mDismissed) { // Don't send a second onDismiss() callback if we've already // dismissed the dialog manually in dismissInternal() onDismiss(mDialog); } mDialog = null; mDialogCreated = false; } } }