Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing listeners via Bundle in AlertDialogFragment - is it possible?

Tags:

java

android

I have a simple class:

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();
    }


}

Now I have to set on button click listeners in SimpleDialogFragment directly, because I can't pass the listeners via Bundle (args). But I want to - so it would look like instantiating an 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);

But now I should set listeners this way:

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);

Perhaps setting on button click listeners directly to DialogFragment instance, rather than passing them via Bundle arguments, is not safe, because the recommended way to pass arguments to Fragment is passing them via Bundle arguments.

And I know that the recommended way to communicate with Fragments in Android is to oblige host activity to implement callback interface. But this way it's not clear that Activity should implement this interface until ClassCastException will be thrown in runtime. And it also makes strong dependence - to use it somewhere outside Activity I should implement the Callback interface in Activity. So I cannot use it in Fragments "independent" of host Activities: prepareAlertDialogFragment().show(getActivity().getSupportFragmentManager(), "tag");

like image 385
Leonid Semyonov Avatar asked Nov 13 '14 09:11

Leonid Semyonov


2 Answers

From what it sounds like you want to have an alert dialog that can have it's own listener which can respond to button press events (kind of like OnClickListener). The way that I have achieved this is by creating a custom DialogFragment along with a listener which extends Parcelable.

ConfirmOrCancelDialogFragment.java

This is your dialog implementation. It's treated very similar to fragments except the way it's instantiated which is through a static method call to 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

This is your listener implementation. You could always add more to this, but just make sure it extends Parcelable so it can be passed through the bundle in the newInstance method found in ConfirmOrCancelDialogFragment.java.

public interface ConfirmOrCancelDialogListener extends Parcelable {
    void onConfirmButtonPressed();

    void onCancelButtonPressed();
}

Usage Example

This is where things get a little messier than I would like. Since your listener is extending Parcelable, you also have to override those methods as well which are describeContents and writeToParcel. Luckily, they can be mostly blank and everything still works fine.

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");

This doesn't completely answer your question of passing them in through an AlertDialogFragment, but I figure if this question has gone unanswered this long, it's worth giving an example of how to accomplish task with a custom Dialog, which seems to give you a little more control over the style and functionality anyway.

like image 187
CodyEngel Avatar answered Nov 15 '22 07:11

CodyEngel


In case you needed Kotlin version with few upgrades, here it is:

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()
        }
    }
}

Then you start it this way:

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)

You might also want to ignore the listener parameter in case you only need to inform user about something that happened. Happy coding!

like image 24
Kostek Avatar answered Nov 15 '22 07:11

Kostek