Java
Android

Dialogの実装を統一化するプラクティス

More than 1 year has passed since last update.

概要

AndroidでDialogを実装するとき、Activityにinner classとしてDialogFragmentを実装する方法をよく使っていましたが、Activityのステップ数が多くなってしまうのと、Dialogがどこにあるのかわかりづらいと感じていたので、Dialogの実装を統一化するプラクティスを考えました。

詳細

Activity内にDialogを実装するのではなくDialog表示のためのFactoryを使う。
DialogのshowもActivityではなく共通クラスに任せる。
ソースコードはGitHubより取得お願いします。
https://github.com/ken-maki/k_commons.dialog

構成は以下。

com.android.k.commons.dialog
┣lib
┃┣BaseDialogFactory
┃┃ DialogのBaseFactoryクラス、Dialog生成に関する共通的な実装はここに書く。
┃┣AlertDialogFactory
┃┃ AlertDialog実装用の親Factoryクラス、AlertDialogを新規作成するときはこのクラスを継承する。
┃┣DialogGenerator
┃┃ Dialog表示を担うクラス、Factoryを渡してあげてDialogさせる。
┃┃ Dialog生成時の制御(例えば、重複表示を抑止)の実装はここに書く。
┃┗CommonAlertDialog
┃  AlertDialog本体。引数に取ったFactoryからBuilderを取得しonCreateDialogのBuilderとして使う。
┣mydialog
┃┣MySampleFooDialogFactory
┃┃ サンプル用DialogFactory、ボタンのクリックイベントをFactory内で実装するパターン。
┃┗MySampleBarDialogFactory
┃  サンプル用DialogFactory、ボタンのクリックイベントをFactoryに設定されたリスナで実装するパターン。
┗MainActivity
   ダイアログ表示のサンプルActivity。
   MySampleBarDialogFactory表示のときに自前でOnClickListenerを実装する。

ソースの解説

ライブラリ部分

AlertDialogFactory.java
abstract class BaseDialogFactory implements Serializable {

    DialogInterface.OnCancelListener mOnCancelListener;
    DialogInterface.OnDismissListener mOnDismissListener;

    /**
     * Activity to display Dialog.
     */
    private Activity mActivity;

    /**
     * Constructor.
     *
     * @param activity create dialog for activity.
     */
    BaseDialogFactory(Activity activity) {
        if (activity == null) throw new IllegalArgumentException("activity is null.");
        mActivity = activity;
    }

    /**
     * get Activity.
     *
     * @return Activity
     */
    protected Activity getActivity() {
        return mActivity;
    }

    /**
     * get Dialog Tag.
     *
     * @return Dialog Tag
     */
    protected String getTag() {
        return this.getClass().getSimpleName();
    }

    /**
     * set OnCancelListener.
     * @param listener action listener
     */
    public void setOnCancelListener(DialogInterface.OnCancelListener listener) {
        mOnCancelListener = listener;
    }

    /**
     * set OnDismissListener.
     * @param listener action listener
     */
    public void setOnDismissListener(DialogInterface.OnDismissListener listener) {
        mOnDismissListener = listener;
    }
}

DialogFactoryの親クラス、共通的な処理を実装しています。

AlertDialogFactory.java
public abstract class AlertDialogFactory extends BaseDialogFactory {

    protected DialogInterface.OnClickListener mPositiveClickListener;
    protected DialogInterface.OnClickListener mNegativeClickListener;

    /**
     * Constructor.
     *
     * @param activity create dialog for activity.
     */
    public AlertDialogFactory(Activity activity) {
        super(activity);
    }

    /**
     * create AlertDialog.Builder.
     *
     * @return can be Show AlertDialog.Builder
     */
    public abstract AlertDialog.Builder build();

    /**
     * set OnClickListener for PositiveButton click.
     * @param listener action listener
     */
    public void setPositiveOnClickListener(DialogInterface.OnClickListener listener) {
        mPositiveClickListener = listener;
    }

    /**
     * set OnClickListener for NegativeButton click.
     * @param listener action listener
     */
    public void setNegativeClickListener(DialogInterface.OnClickListener listener) {
        mNegativeClickListener = listener;
    }
}

AlertDialogのFactoryクラス。親build()メソッドがこのクラスの主役です。
AlertDialogFactoryを継承したクラスは、build()メソッド内に表示したい情報の
AlertDialog.Builderを生成、返却する実装を作成するだけでOK。
もしAlertDialog以外のDialogを使いたいときはAlertDialogFactoryのような感じで
親Factoryを作成する。(SingleChoiceDialogFactoryとかDatePickerDialogFactoryとか)

CommonAlertDialog.java
public class CommonAlertDialog extends DialogFragment {
    private static final String ARG_KEY_FACTORY = "f";

    AlertDialogFactory mFactory;

    public static CommonAlertDialog newInstance(AlertDialogFactory factory) {
        Bundle bundle = new Bundle();
        bundle.putSerializable(ARG_KEY_FACTORY, factory);

        CommonAlertDialog dialog = new CommonAlertDialog();
        dialog.setArguments(bundle);
        return dialog;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        mFactory = (AlertDialogFactory)
                getArguments().getSerializable(ARG_KEY_FACTORY);
        if (mFactory == null) throw new IllegalArgumentException("factory is null.");

        AlertDialog.Builder builder = mFactory.build();
        if (builder == null) throw new IllegalStateException("AlertDialog.Builder is null.");
        return builder.create();
    }

    @Override
    public void onCancel(DialogInterface dialog) {
        super.onCancel(dialog);
        if (mFactory.mOnCancelListener != null) {
            mFactory.mOnCancelListener.onCancel(dialog);
        }
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        if (mFactory.mOnDismissListener != null) {
            mFactory.mOnDismissListener.onDismiss(dialog);
        }
    }

    /**
     * return AlertDialogFactory.
     * @return AlertDialogFactory
     */
    public AlertDialogFactory getFactory() {
        return mFactory;
    }
}

Dialog本体。
やってることは大まかに以下の2つ。
・引数からFactoryをとってきて、createする。
・DialogInterfaceのイベントが設定されてたら実行する。

個々の実装部分

MySampleFooDialogFactory.java
public class MySampleFooDialogFactory extends AlertDialogFactory {

    public MySampleFooDialogFactory(Activity activity) {
        super(activity);
    }

    @Override
    public AlertDialog.Builder build() {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

        builder.setTitle("Foo Dialog");
        builder.setMessage("This Foo Dialog. please Button Click.");
        builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Toast.makeText(getActivity(), "Ok clicked.(listener setting from factory)", Toast.LENGTH_SHORT).show();
                dialog.dismiss();
            }
        });
        builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Toast.makeText(getActivity(), "Cancel clicked.(listener setting from factory)", Toast.LENGTH_SHORT).show();
                dialog.dismiss();
            }
        });

        builder.setCancelable(false);
        return builder;
    }
}

AlertDialogFactoryを継承してbuild()を実装するだけ。
サンプルは簡素な実装ですが、カスタムレイアウトなどを使う複雑なDialogのときも
処理はbuild()に実装。
こうすることでDialog作成時はDialogの中身を作ることだけに集中できる。

メリット

-Dialog実装する人はAlertDialog.Builderを生成するコードの実装だけに集中できる。
 共通処理はDialogGeneratorなりAlertDialogFactoryなりCommonAlertDialogなりに
 実装することで、AlertDialog全体が統一された動作になる。
-Activity内に直接Dialogの実装とDialogをshowする実装が無くなる
 Activityと疎結合になるのでinner classでの実装に比べDialog側の変更がしやすくなるはず。

終わりに

Qiita初投稿でしたが、こんな感じの記事でいいのかなぁ。
試行錯誤で今後も記事投稿していきますので、生暖かい目で見守ってくださると幸いです。

ソースコード
https://github.com/ken-maki/k_commons.dialog