public abstract class

PreferenceDialogFragmentCompat

extends DialogFragment

 java.lang.Object

androidx.fragment.app.Fragment

androidx.fragment.app.DialogFragment

↳androidx.preference.PreferenceDialogFragmentCompat

Subclasses:

ListPreferenceDialogFragmentCompat, EditTextPreferenceDialogFragmentCompat, MultiSelectListPreferenceDialogFragmentCompat

Gradle dependencies

compile group: 'androidx.preference', name: 'preference', version: '1.2.1'

  • groupId: androidx.preference
  • artifactId: preference
  • version: 1.2.1

Artifact androidx.preference:preference:1.2.1 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.preference:preference com.android.support:preference-v7

Androidx class mapping:

androidx.preference.PreferenceDialogFragmentCompat android.support.v7.preference.PreferenceDialogFragmentCompat

Overview

Abstract base class which presents a dialog associated with a DialogPreference. Since the preference object may not be available during fragment re-creation, the necessary information for displaying the dialog is read once during the initial call to PreferenceDialogFragmentCompat.onCreate(Bundle) and saved/restored in the saved instance state. Custom subclasses should also follow this pattern.

Summary

Fields
protected static final java.lang.StringARG_KEY

from DialogFragmentSTYLE_NO_FRAME, STYLE_NO_INPUT, STYLE_NO_TITLE, STYLE_NORMAL
from FragmentmPreviousWho
Constructors
publicPreferenceDialogFragmentCompat()

Methods
public DialogPreferencegetPreference()

Get the preference that requested this dialog.

protected booleanneedInputMethod()

Returns whether the preference needs to display a soft input method when the dialog is displayed.

protected voidonBindDialogView(View view)

Binds views in the content view of the dialog to data.

public voidonClick(DialogInterface dialog, int which)

public voidonCreate(Bundle savedInstanceState)

Called to do initial creation of a fragment.

public DialogonCreateDialog(Bundle savedInstanceState)

Override to build your own custom Dialog container.

protected ViewonCreateDialogView(Context context)

Creates the content view for the dialog (if a custom content view is required).

public abstract voidonDialogClosed(boolean positiveResult)

public voidonDismiss(DialogInterface dialog)

protected voidonPrepareDialogBuilder(AlertDialog.Builder builder)

Prepares the dialog builder to be shown when the preference is clicked.

public voidonSaveInstanceState(Bundle outState)

Called to ask the fragment to save its current dynamic state, so it can later be reconstructed in a new instance if its process is restarted.

protected voidscheduleShowSoftInput()

Uses to schedule showing soft input method when the dialog has an editor focused.

from DialogFragmentdismiss, dismissAllowingStateLoss, dismissNow, getDialog, getShowsDialog, getTheme, isCancelable, onActivityCreated, onAttach, onCancel, onDestroyView, onDetach, onGetLayoutInflater, onStart, onStop, onViewStateRestored, requireComponentDialog, requireDialog, setCancelable, setShowsDialog, setStyle, setupDialog, show, show, showNow
from Fragmentdump, equals, getActivity, getAllowEnterTransitionOverlap, getAllowReturnTransitionOverlap, getArguments, getChildFragmentManager, getContext, getDefaultViewModelCreationExtras, getDefaultViewModelProviderFactory, getEnterTransition, getExitTransition, getFragmentManager, getHost, getId, getLayoutInflater, getLayoutInflater, getLifecycle, getLoaderManager, getParentFragment, getParentFragmentManager, getReenterTransition, getResources, getRetainInstance, getReturnTransition, getSavedStateRegistry, getSharedElementEnterTransition, getSharedElementReturnTransition, getString, getString, getTag, getTargetFragment, getTargetRequestCode, getText, getUserVisibleHint, getView, getViewLifecycleOwner, getViewLifecycleOwnerLiveData, getViewModelStore, hashCode, hasOptionsMenu, instantiate, instantiate, isAdded, isDetached, isHidden, isInLayout, isMenuVisible, isRemoving, isResumed, isStateSaved, isVisible, onActivityResult, onAttach, onAttachFragment, onConfigurationChanged, onContextItemSelected, onCreateAnimation, onCreateAnimator, onCreateContextMenu, onCreateOptionsMenu, onCreateView, onDestroy, onDestroyOptionsMenu, onHiddenChanged, onInflate, onInflate, onLowMemory, onMultiWindowModeChanged, onOptionsItemSelected, onOptionsMenuClosed, onPause, onPictureInPictureModeChanged, onPrepareOptionsMenu, onPrimaryNavigationFragmentChanged, onRequestPermissionsResult, onResume, onViewCreated, postponeEnterTransition, postponeEnterTransition, registerForActivityResult, registerForActivityResult, registerForContextMenu, requestPermissions, requireActivity, requireArguments, requireContext, requireFragmentManager, requireHost, requireParentFragment, requireView, setAllowEnterTransitionOverlap, setAllowReturnTransitionOverlap, setArguments, setEnterSharedElementCallback, setEnterTransition, setExitSharedElementCallback, setExitTransition, setHasOptionsMenu, setInitialSavedState, setMenuVisibility, setReenterTransition, setRetainInstance, setReturnTransition, setSharedElementEnterTransition, setSharedElementReturnTransition, setTargetFragment, setUserVisibleHint, shouldShowRequestPermissionRationale, startActivity, startActivity, startActivityForResult, startActivityForResult, startIntentSenderForResult, startPostponedEnterTransition, toString, unregisterForContextMenu
from java.lang.Objectclone, finalize, getClass, notify, notifyAll, wait, wait, wait

Fields

protected static final java.lang.String ARG_KEY

Constructors

public PreferenceDialogFragmentCompat()

Methods

public void onCreate(Bundle savedInstanceState)

Called to do initial creation of a fragment. This is called after Fragment.onAttach(Activity) and before Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle).

Note that this can be called while the fragment's activity is still in the process of being created. As such, you can not rely on things like the activity's content view hierarchy being initialized at this point. If you want to do work once the activity itself is created, add a LifecycleObserver on the activity's Lifecycle, removing it when it receives the callback.

Any restored child fragments will be created before the base Fragment.onCreate method returns.

Parameters:

savedInstanceState: If the fragment is being re-created from a previous saved state, this is the state.

public void onSaveInstanceState(Bundle outState)

Called to ask the fragment to save its current dynamic state, so it can later be reconstructed in a new instance if its process is restarted. If a new instance of the fragment later needs to be created, the data you place in the Bundle here will be available in the Bundle given to Fragment.onCreate(Bundle), Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle), and Fragment.onViewCreated(View, Bundle).

This corresponds to and most of the discussion there applies here as well. Note however: this method may be called at any time before Fragment.onDestroy(). There are many situations where a fragment may be mostly torn down (such as when placed on the back stack with no UI showing), but its state will not be saved until its owning activity actually needs to save its state.

Parameters:

outState: Bundle in which to place your saved state.

public Dialog onCreateDialog(Bundle savedInstanceState)

Override to build your own custom Dialog container. This is typically used to show an AlertDialog instead of a generic Dialog; when doing so, Fragment.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 DialogFragment.onCreate(Bundle) and immediately before Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle). The default implementation simply instantiates and returns a Dialog class.

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

Parameters:

savedInstanceState: The last saved instance state of the Fragment, or null if this is a freshly created Fragment.

Returns:

Return a new Dialog instance to be displayed by the Fragment.

public DialogPreference getPreference()

Get the preference that requested this dialog. Available after PreferenceDialogFragmentCompat.onCreate(Bundle) has been called on the PreferenceFragmentCompat which launched this dialog.

Returns:

The DialogPreference associated with this dialog

protected void onPrepareDialogBuilder(AlertDialog.Builder builder)

Prepares the dialog builder to be shown when the preference is clicked. Use this to set custom properties on the dialog.

Do not or .

protected boolean needInputMethod()

Returns whether the preference needs to display a soft input method when the dialog is displayed. Default is false. Subclasses should override this method if they need the soft input method brought up automatically.

Note: If your application targets P or above, ensure your subclass manually requests focus (ideally in PreferenceDialogFragmentCompat.onBindDialogView(View)) for the input field in order to correctly attach the input method to the field.

protected void scheduleShowSoftInput()

Uses to schedule showing soft input method when the dialog has an editor focused.

Note that starting from Android R, the new WindowInsets API supports showing soft-input on-demand, so there is no longer a need to schedule showing soft-input when input connection established by the focused editor.

protected View onCreateDialogView(Context context)

Creates the content view for the dialog (if a custom content view is required). By default, it inflates the dialog layout resource if it is set.

Returns:

The content view for the dialog

See also: Preference.setLayoutResource(int)

protected void onBindDialogView(View view)

Binds views in the content view of the dialog to data.

Make sure to call through to the superclass implementation.

Parameters:

view: The content view of the dialog, if it is custom

public void onClick(DialogInterface dialog, int which)

public void onDismiss(DialogInterface dialog)

public abstract void onDialogClosed(boolean positiveResult)

Source

/*
 * 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.preference;

import static androidx.annotation.RestrictTo.Scope.LIBRARY;

import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.Window;
import android.view.WindowInsets;
import android.widget.TextView;

import androidx.annotation.DoNotInline;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;

/**
 * Abstract base class which presents a dialog associated with a {@link DialogPreference}. Since
 * the preference object may not be available during fragment re-creation, the necessary
 * information for displaying the dialog is read once during the initial call to
 * {@link #onCreate(Bundle)} and saved/restored in the saved instance state. Custom subclasses
 * should also follow this pattern.
 */
public abstract class PreferenceDialogFragmentCompat extends DialogFragment implements
        DialogInterface.OnClickListener {

    protected static final String ARG_KEY = "key";

    private static final String SAVE_STATE_TITLE = "PreferenceDialogFragment.title";
    private static final String SAVE_STATE_POSITIVE_TEXT = "PreferenceDialogFragment.positiveText";
    private static final String SAVE_STATE_NEGATIVE_TEXT = "PreferenceDialogFragment.negativeText";
    private static final String SAVE_STATE_MESSAGE = "PreferenceDialogFragment.message";
    private static final String SAVE_STATE_LAYOUT = "PreferenceDialogFragment.layout";
    private static final String SAVE_STATE_ICON = "PreferenceDialogFragment.icon";

    private DialogPreference mPreference;

    private CharSequence mDialogTitle;
    private CharSequence mPositiveButtonText;
    private CharSequence mNegativeButtonText;
    private CharSequence mDialogMessage;
    private @LayoutRes int mDialogLayoutRes;

    private BitmapDrawable mDialogIcon;

    /** Which button was clicked. */
    private int mWhichButtonClicked;

    @SuppressWarnings("deprecation")
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final Fragment rawFragment = getTargetFragment();
        if (!(rawFragment instanceof DialogPreference.TargetFragment)) {
            throw new IllegalStateException("Target fragment must implement TargetFragment" +
                    " interface");
        }

        final DialogPreference.TargetFragment fragment =
                (DialogPreference.TargetFragment) rawFragment;

        final String key = requireArguments().getString(ARG_KEY);
        if (savedInstanceState == null) {
            mPreference = fragment.findPreference(key);
            mDialogTitle = mPreference.getDialogTitle();
            mPositiveButtonText = mPreference.getPositiveButtonText();
            mNegativeButtonText = mPreference.getNegativeButtonText();
            mDialogMessage = mPreference.getDialogMessage();
            mDialogLayoutRes = mPreference.getDialogLayoutResource();

            final Drawable icon = mPreference.getDialogIcon();
            if (icon == null || icon instanceof BitmapDrawable) {
                mDialogIcon = (BitmapDrawable) icon;
            } else {
                final Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
                        icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
                final Canvas canvas = new Canvas(bitmap);
                icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
                icon.draw(canvas);
                mDialogIcon = new BitmapDrawable(getResources(), bitmap);
            }
        } else {
            mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE);
            mPositiveButtonText = savedInstanceState.getCharSequence(SAVE_STATE_POSITIVE_TEXT);
            mNegativeButtonText = savedInstanceState.getCharSequence(SAVE_STATE_NEGATIVE_TEXT);
            mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE);
            mDialogLayoutRes = savedInstanceState.getInt(SAVE_STATE_LAYOUT, 0);
            final Bitmap bitmap = savedInstanceState.getParcelable(SAVE_STATE_ICON);
            if (bitmap != null) {
                mDialogIcon = new BitmapDrawable(getResources(), bitmap);
            }
        }
    }

    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);

        outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle);
        outState.putCharSequence(SAVE_STATE_POSITIVE_TEXT, mPositiveButtonText);
        outState.putCharSequence(SAVE_STATE_NEGATIVE_TEXT, mNegativeButtonText);
        outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage);
        outState.putInt(SAVE_STATE_LAYOUT, mDialogLayoutRes);
        if (mDialogIcon != null) {
            outState.putParcelable(SAVE_STATE_ICON, mDialogIcon.getBitmap());
        }
    }

    @Override
    public @NonNull
    Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;

        final AlertDialog.Builder builder = new AlertDialog.Builder(requireContext())
                .setTitle(mDialogTitle)
                .setIcon(mDialogIcon)
                .setPositiveButton(mPositiveButtonText, this)
                .setNegativeButton(mNegativeButtonText, this);

        View contentView = onCreateDialogView(requireContext());
        if (contentView != null) {
            onBindDialogView(contentView);
            builder.setView(contentView);
        } else {
            builder.setMessage(mDialogMessage);
        }

        onPrepareDialogBuilder(builder);

        // Create the dialog
        final Dialog dialog = builder.create();
        if (needInputMethod()) {
            requestInputMethod(dialog);
        }

        return dialog;
    }

    /**
     * Get the preference that requested this dialog. Available after {@link #onCreate(Bundle)} has
     * been called on the {@link PreferenceFragmentCompat} which launched this dialog.
     *
     * @return The {@link DialogPreference} associated with this dialog
     */
    @SuppressWarnings("deprecation")
    public DialogPreference getPreference() {
        if (mPreference == null) {
            final String key = requireArguments().getString(ARG_KEY);
            final DialogPreference.TargetFragment fragment =
                    (DialogPreference.TargetFragment) getTargetFragment();
            mPreference = fragment.findPreference(key);
        }
        return mPreference;
    }

    /**
     * Prepares the dialog builder to be shown when the preference is clicked.
     * Use this to set custom properties on the dialog.
     *
     * <p>Do not {@link AlertDialog.Builder#create()} or {@link AlertDialog.Builder#show()}.
     */
    protected void onPrepareDialogBuilder(@NonNull AlertDialog.Builder builder) {}

    /**
     * Returns whether the preference needs to display a soft input method when the dialog is
     * displayed. Default is false. Subclasses should override this method if they need the soft
     * input method brought up automatically.
     *
     * <p>Note: If your application targets P or above, ensure your subclass manually requests
     * focus (ideally in {@link #onBindDialogView(View)}) for the input field in order to
     * correctly attach the input method to the field.
     *
     * @hide
     */
    @RestrictTo(LIBRARY)
    protected boolean needInputMethod() {
        return false;
    }

    /**
     * Uses to schedule showing soft input method when the dialog has an editor focused.
     * <p>
     * Note that starting from Android R, the new WindowInsets API supports showing soft-input
     * on-demand, so there is no longer a need to schedule showing soft-input when input connection
     * established by the focused editor.</p>
     * @hide
     */
    @RestrictTo(LIBRARY)
    protected void scheduleShowSoftInput() {
    }

    /**
     * Sets the required flags on the dialog window to enable input method window to show up.
     * <p>
     * Note that starting from Android R, the new WindowInsets API supports showing soft-input
     * on-demand, so there is no longer a need to schedule showing soft-input when input connection
     * established by the focused editor.</p>
     */
    private void requestInputMethod(@NonNull Dialog dialog) {
        Window window = dialog.getWindow();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            Api30Impl.showIme(window);
        } else {
            scheduleShowSoftInput();
        }
    }

    /**
     * Creates the content view for the dialog (if a custom content view is required).
     * By default, it inflates the dialog layout resource if it is set.
     *
     * @return The content view for the dialog
     * @see DialogPreference#setLayoutResource(int)
     */
    @Nullable
    protected View onCreateDialogView(@NonNull Context context) {
        final int resId = mDialogLayoutRes;
        if (resId == 0) {
            return null;
        }

        return getLayoutInflater().inflate(resId, null);
    }

    /**
     * Binds views in the content view of the dialog to data.
     *
     * <p>Make sure to call through to the superclass implementation.
     *
     * @param view The content view of the dialog, if it is custom
     */
    protected void onBindDialogView(@NonNull View view) {
        View dialogMessageView = view.findViewById(android.R.id.message);

        if (dialogMessageView != null) {
            final CharSequence message = mDialogMessage;
            int newVisibility = View.GONE;

            if (!TextUtils.isEmpty(message)) {
                if (dialogMessageView instanceof TextView) {
                    ((TextView) dialogMessageView).setText(message);
                }

                newVisibility = View.VISIBLE;
            }

            if (dialogMessageView.getVisibility() != newVisibility) {
                dialogMessageView.setVisibility(newVisibility);
            }
        }
    }

    @Override
    public void onClick(@NonNull DialogInterface dialog, int which) {
        mWhichButtonClicked = which;
    }

    @Override
    public void onDismiss(@NonNull DialogInterface dialog) {
        super.onDismiss(dialog);
        onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
    }

    public abstract void onDialogClosed(boolean positiveResult);

    /**
     * Nested class to avoid verification errors for methods introduced in R.
     */
    @RequiresApi(Build.VERSION_CODES.R)
    private static class Api30Impl {
        // Prevent instantiation.
        private Api30Impl() {}

        /**
         * Shows the IME on demand for the given {@link Window}.
         */
        @DoNotInline
        static void showIme(@NonNull Window dialogWindow) {
            dialogWindow.getDecorView().getWindowInsetsController().show(WindowInsets.Type.ime());
        }
    }
}