Передача слушателей через Bundle в AlertDialogFragment - возможно ли это?

У меня есть простой класс:

public class AlertDialogFragment extends DialogFragment {

    private static final DialogInterface.OnClickListener DUMMY_ON_BUTTON_CLICKED_LISTENER = new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            // do nothing
        }
    };

    public static final class Builder implements Parcelable {

        public static final Creator<Builder> CREATOR = new Creator<Builder>() {
            @Override
            public Builder createFromParcel(Parcel source) {
                return new Builder(source);
            }

            @Override
            public Builder[] newArray(int size) {
                return new Builder[size];
            }
        };

        private Optional<Integer> title;
        private Optional<Integer> message;
        private Optional<Integer> positiveButtonText;
        private Optional<Integer> negativeButtonText;

        public Builder() {
            title = Optional.absent();
            message = Optional.absent();
            positiveButtonText = Optional.absent();
            negativeButtonText = Optional.absent();
        }

        public Builder(Parcel in) {
            title = (Optional<Integer>) in.readSerializable();
            message = (Optional<Integer>) in.readSerializable();
            positiveButtonText = (Optional<Integer>) in.readSerializable();
            negativeButtonText = (Optional<Integer>) in.readSerializable();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            out.writeSerializable(title);
            out.writeSerializable(message);
            out.writeSerializable(positiveButtonText);
            out.writeSerializable(negativeButtonText);
        }

        @Override
        public int describeContents() {
            return 0;
        }

        public Builder withTitle(Integer title) {
            this.title = Optional.fromNullable(title);
            return this;
        }

        public Builder withMessage(Integer message) {
            this.message = Optional.fromNullable(message);
            return this;
        }

        public Builder withPositiveButton(int buttonText) {
            this.positiveButtonText = Optional.fromNullable(buttonText);
            return this;
        }

        public Builder withNegativeButton(int buttonText) {
            this.negativeButtonText = Optional.fromNullable(buttonText);
            return this;
        }

        private void set(AlertDialog.Builder dialogBuilder, final AlertDialogFragment alertDialogFragment) {
            if (title.isPresent()) {
                dialogBuilder.setTitle(title.get());
            }
            if (message.isPresent()) {
                dialogBuilder.setMessage(message.get());
            }
            if (positiveButtonText.isPresent()) {
                dialogBuilder.setPositiveButton(positiveButtonText.get(), new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        alertDialogFragment.onPositiveButtonClickedListener.onClick(dialog, which);
                    }
                });
            }
            if (negativeButtonText.isPresent()) {
                dialogBuilder.setNegativeButton(negativeButtonText.get(), new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        alertDialogFragment.onNegativeButtonClickedListener.onClick(dialog, which);
                    }
                });
            }
        }

        public AlertDialogFragment build() {
            return AlertDialogFragment.newInstance(this);
        }
    }


    private static final String KEY_BUILDER = "builder";

    private DialogInterface.OnClickListener onPositiveButtonClickedListener = DUMMY_ON_BUTTON_CLICKED_LISTENER;
    private DialogInterface.OnClickListener onNegativeButtonClickedListener = DUMMY_ON_BUTTON_CLICKED_LISTENER;


    private static AlertDialogFragment newInstance(Builder builder) {
        Bundle args = new Bundle();
        args.putParcelable(KEY_BUILDER, builder);
        AlertDialogFragment fragment = new AlertDialogFragment();
        fragment.setArguments(args);
        return fragment;
    }

    public void setOnPositiveButtonClickedListener(DialogInterface.OnClickListener listener) {
        this.onPositiveButtonClickedListener = listener != null ? listener : DUMMY_ON_BUTTON_CLICKED_LISTENER;
    }

    public void setOnNegativeButtonClickedListener(DialogInterface.OnClickListener listener) {
        this.onNegativeButtonClickedListener = listener != null ? listener : DUMMY_ON_BUTTON_CLICKED_LISTENER;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
        Builder builder = getArguments().getParcelable(KEY_BUILDER);
        builder.set(alertDialogBuilder, this);
        return alertDialogBuilder.create();
    }


}

Теперь мне нужно напрямую нажимать кнопку "слушать" в SimpleDialogFragment, потому что я не могу передать слушателей через Bundle (args). Но я хочу - так он будет выглядеть как экземпляр AlertDialog:

AlertDialogFragment dialogFragment = new AlertDialogFragment.Builder()
                .withTitle(R.string.no_internet_connection)
                .withMessage(messageId)
                .withPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                }).build();
dialogFragment.show(getSupportFragmentManager(), FRAGMENT_TAG_NO_INTERNET_CONNECTION);

Но теперь я должен установить слушателей таким образом:

AlertDialogFragment dialogFragment = new AlertDialogFragment.Builder()
                .withTitle(R.string.no_internet_connection)
                .withMessage(messageId)
                .withPositiveButton(android.R.string.ok)
                .build();
dialogFragment.setOnPositiveButtonClickListener(new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                });
dialogFragment.show(getSupportFragmentManager(), FRAGMENT_TAG_NO_INTERNET_CONNECTION);

Возможно, установка на кнопки щелчка непосредственно на экземпляр DialogFragment вместо передачи через аргументы Bundle небезопасна, так как рекомендуемый способ передать аргументы Fragment передаёт их через аргументы Bundle.

И я знаю, что рекомендуемый способ общения с Fragment в Android - обязать активность хоста для реализации интерфейса обратного вызова. Но это не ясно, что Activity должно реализовать этот интерфейс до тех пор, пока ClassCastException не будет запущен во время выполнения. И это также делает сильную зависимость - использовать ее где-то вне Activity. Я должен реализовать интерфейс Callback в Activity. Поэтому я не могу использовать его в Fragment "независимый" от хоста. Действия: prepareAlertDialogFragment().show(getActivity().getSupportFragmentManager(), "tag");

Ответ 1

Похоже, что вы хотите иметь диалоговое окно оповещения, которое может иметь собственный слушатель, который может реагировать на события нажатия кнопки (вроде как OnClickListener). Способ, которым я достиг этого, - создать пользовательский диалог DialogFragment вместе со слушателем, который расширяет Parcelable.

ConfirmOrCancelDialogFragment.java

Это ваша реализация диалога. Он обрабатывался очень похоже на фрагменты, кроме способа его создания, который через статический метод вызывает newInstance.

public class ConfirmOrCancelDialogFragment extends DialogFragment {
    TextView tvDialogHeader,
            tvDialogBody;

    Button bConfirm,
            bCancel;

    private ConfirmOrCancelDialogListener mListener;

    private String mTitle,
            mBody,
            mConfirmButton,
            mCancelButton;

    public ConfirmOrCancelDialogFragment() {
    }

    public static ConfirmOrCancelDialogFragment newInstance(String title, String body, ConfirmOrCancelDialogListener listener) {
        ConfirmOrCancelDialogFragment fragment = new ConfirmOrCancelDialogFragment();
        Bundle args = new Bundle();
        args.putString("title", title);
        args.putString("body", body);
        args.putParcelable("listener", listener);
        fragment.setArguments(args);
        return fragment;
    }

    public static ConfirmOrCancelDialogFragment newInstance(String title, String body, String confirmButton, String cancelButton, ConfirmOrCancelDialogListener listener) {
        ConfirmOrCancelDialogFragment fragment = new ConfirmOrCancelDialogFragment();
        Bundle args = new Bundle();
        args.putString("title", title);
        args.putString("body", body);
        args.putString("confirmButton", confirmButton);
        args.putString("cancelButton", cancelButton);
        args.putParcelable("listener", listener);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.dialog_confirm_or_cancel, container);

        /* Initial Dialog Setup */
        getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); // we are using a textview for the title
        mListener = getArguments().getParcelable("listener");

        /* Link UI */
        tvDialogHeader = (TextView) view.findViewById(R.id.tvDialogHeader);
        tvDialogBody = (TextView) view.findViewById(R.id.tvDialogBody);
        bConfirm = (Button) view.findViewById(R.id.bConfirm);
        bCancel = (Button) view.findViewById(R.id.bCancel);

        /* Setup UI */
        mTitle = getArguments().getString("title", "");
        mBody = getArguments().getString("body", "");
        mConfirmButton = getArguments().getString("confirmButton", getResources().getString(R.string.yes_delete));
        mCancelButton = getArguments().getString("cancelButton", getResources().getString(R.string.no_do_not_delete));

        tvDialogHeader.setText(mTitle);
        tvDialogBody.setText(mBody);
        bConfirm.setText(mConfirmButton);
        bCancel.setText(mCancelButton);

        bConfirm.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mListener.onConfirmButtonPressed();
                dismiss();
            }
        });

        bCancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mListener.onCancelButtonPressed();
                dismiss();
            }
        });

        return view;
    }
}

ConfirmOrCancelDialogListener.java

Это ваша реализация-слушатель, вы всегда можете добавить к ней больше, но просто убедитесь, что она расширяет Parcelable, поэтому ее можно передать через пакет в методе newInstance, найденном в ConfirmOrCancelDialogFragment.java

public interface ConfirmOrCancelDialogListener extends Parcelable {
    void onConfirmButtonPressed();

    void onCancelButtonPressed();
}

Пример использования:

Здесь все становится немного грязнее, чем хотелось бы. Поскольку ваш слушатель расширяет Parcelable, вам также необходимо переопределить те методы, которые описываютContents и writeToParcel. К счастью, они могут быть в основном пустыми, и все работает нормально.

FragmentManager fm = getActivity().getSupportFragmentManager();
ConfirmOrCancelDialogFragment confirmOrCancelDialogFragment = ConfirmOrCancelDialogFragment.newInstance
    (getString(R.string.header), getString(R.string.body),
                        new ConfirmOrCancelDialogListener() {
                            @Override
                            public void onConfirmButtonPressed() {

                            }

                            public void onCancelButtonPressed() {
                            }

                            @Override
                            public int describeContents() {
                                return 0;
                            }

                            @Override
                            public void writeToParcel(Parcel dest, int flags) {
                            }
                        }
                );
confirmOrCancelDialogFragment.show(fm, "fragment_delete_confirmation");

Это не полностью отвечает на ваш вопрос о передаче их через AlertDialogFragment, но я полагаю, что если этот вопрос остался без ответа, то долгое время стоит привести пример того, как выполнить задачу с помощью настраиваемого диалога, который, кажется, дает вам немного больше контроля над стилем и функциональностью в любом случае.

Ответ 2

Если вам нужна версия Kotlin с несколькими обновлениями, вот она:

class AlertDialogFragment : DialogFragment() {
    companion object {
        internal fun newInstance(
            exampleParameter: String,
            listener: AlertDialogListener? = null
        ) = AlertDialogFragment().apply {
            this.arguments = Bundle().apply {
                this.putString(EXAMPLE_PARAMETER, exampleParameter)
                this.putParcelable(ALERT_LISTENER, listener)
            }
        }

        interface AlertDialogListener: Parcelable {
            fun primaryActionClicked()
            fun secondaryActionClicked() { /* nop */ }
            override fun describeContents(): Int = 0
            override fun writeToParcel(dest: Parcel, flags: Int) { /* nop */ }
        }

        const val EXAMPLE_PARAMETER = "example_parameter"
        const val ALERT_LISTENER = "alert_listener"
    }

    private var listener: AlertDialogListener? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        return inflater.inflate(R.layout.dialog_alert, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        initListeners()
        initExampleParameter()
    }

    private fun initListeners() {
        val listener = arguments!!.getParcelable<AlertDialogListener>(ALERT_LISTENER)
        if (listener != null) {
            this.listener = listener
        }
    }

    private fun initExampleParameter() {
        example_view.text = arguments!!.getString(EXAMPLE_PARAMETER)!!
        example_view.setOnClickListener {
            listener?.primaryActionClicked()
            dismiss()
        }
    }
}

Затем вы начинаете это так:

AlertDialogFragment.newInstance(
            getString(R.string.example_parameter),
            object : AlertDialogFragment.Companion.AlertDialogListener {
                override fun primaryActionClicked() {
                    // DO SOMETHING ABOUT THE ACTION
                }
            }
        ).show(childFragmentManager, AlertDialogFragment::class.java.name)

Вы также можете игнорировать параметр listener, если вам нужно только проинформировать пользователя о том, что произошло. Удачного кодирования!