8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ボタン連打でDialogFragmentを多重に表示させない為のナニか

Last updated at Posted at 2018-11-17

ボタン連打でDialogFragmentを多重に表示させない

  • AndroidのUIで、ボタン押下によりダイアログを表示したいと思います。
  • 以下のように実装したところ、ボタン連打をすることでDialogが複数表示されてしまいました(ToT)
MainActivity.java
  int id = 0;
  @OnClick(R.id.button)
  public void onClick(Button button) {
     MyDialogFragment dialog = MyDialogFragment.newInstance(id++);
     dialog.show(getSupportFragmentManager() , "dialog");
  }
  • 原因としては、
    • ダイアログ表示が遅い(onCreateDialogに500msec程度かかる)
    • ダイアログ表示中かどうかをそもそもチェックしていない。
      でした。

絶対コレ悩んでるのは自分だけではないはず

BaseDialogFragmentの実装 (extendsすれば重複が防げるモノ)

  • やりたいこと
    • 表示元のFragmentからDialogFragmentに対して引数を渡せること。
    • DialogFragmentが正常終了(AlertDialogで各ボタンが押下)またはキャンセル(バック、ダイアログ外タップ)された場合、表示元のFragmentに対してonActivityResult()を利用してコールバックできること。
    • コールバック用のListenerFragmentに持たせないこと!
    • DialogFragmentshow or showNowした際に、既に同一のTAGのDialogFragmentが存在する場合は先発優先とすること(後発は無視)
BaseDialogFragment.java
@DebugLog
public abstract class BaseDialogFragment extends DialogFragment {

    private static final String TAG = BaseDialogFragment.class.getSimpleName();

    static BaseDialogFragment newInstance(Class<? extends BaseDialogFragment> clazz, Bundle args) {
        BaseDialogFragment dialog = null;
        try {
            dialog = clazz.getDeclaredConstructor().newInstance();
            dialog.setArguments(args);
        } catch (Exception e) {
            Log.d(TAG, e.getMessage());
        }
        return dialog;
    }

    @Override
    public void show(@NonNull FragmentManager manager, String tag) {
        showNow(manager, tag); //showではなく、showNowを呼ぶ
    }

    @Override
    public void showNow(@NonNull FragmentManager manager, String tag) throws RuntimeException {
        if (isSameTagDialogShowing(manager,tag)) return;
        super.showNow(manager, tag);
    }

    /**
     * 同一TAGのダイアログが表示されているかどうかを判定する。
     * @return 同一TAGのダイアログが表示されている場合は true を返す。
     */
    private boolean isSameTagDialogShowing(@NonNull FragmentManager manager, String tag) {
        Fragment previousFragment = manager.findFragmentByTag(tag);
        if (previousFragment instanceof DialogFragment) {
            Dialog dialog = ((DialogFragment)previousFragment).getDialog();
            if (dialog != null && dialog.isShowing()) {
                return true;
            }
        }
        return false;
    }

    /**
     * AlertDialog用の汎用リスナー
     * @return 汎用リスナー。 呼び出し元のFragmentのonActivityResultを返す。
     */
    DialogInterface.OnClickListener getDefaultClickListener() {
        return (dialog, which) -> {
            dialog.dismiss();
            Intent intent = new Intent();
            if (getArguments() != null) {
                intent.putExtras(getArguments());  // intent に Dialog 実行時の引数を詰める
            }
            if (getTargetFragment() != null) {
                getTargetFragment().onActivityResult(getTargetRequestCode(), which, intent);
            }
        };
    }
}

  • 実際の使用例(BaseDialogFragmentを拡張したDialog)はこちら
    • 基本的には普通のDialogFragmentと同じ。
    • 制約事項: show()およびshowNow()はオーバーライドしてはならない。
MyDialogFragment.java
@DebugLog
public class MyDialogFragment extends BaseDialogFragment {

    private static final String ARGS_TITLE = "title";

    static MyDialogFragment newInstance(String title) {
        Bundle bundle = new Bundle();
        bundle.putString(ARGS_TITLE, title);
        return (MyDialogFragment)BaseDialogFragment.newInstance(MyDialogFragment.class, bundle); //要キャスト
    }

    @Override
    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), getTheme());
        builder.setPositiveButton("OK", getDefaultClickListener())
               .setTitle(getArguments().getString(ARGS_TITLE));
        return builder.create();
    }

}

  • 実際の使用例(Dialogを呼び出すFragment)はこちら
    • DialogFragmentは引数をsetArguments()およびgetArguments()でやり取りする必要がある。
    • setTargetFragment()で指定したFragmentに対してonActivityResult()をコールバックする。
MainFragment.java

    @OnClick(R.id.button)
    public void OnButtonClicked(Button b) {
        MyDialogFragment dialog = MyDialogFragment.newInstance("MyTitle");
        dialog.setTargetFragment(this, 100);
        dialog.show(getFragmentManager(), "dialog");
    }

    @Override
    public void onActivityResult(int targetRequestCode, int which, Intent intent) {
        //TODO: FragmationDialogが終了した際の処理を記載
    }

デバッグ時に超助かったライブラリ Hugo

  • デバッグ時にメソッドがどの順序で呼ばれたり呼ばれなかったりしたのか、Logcatに出力してくれるライブラリ。

  • クラスやメソッドに @DebugLogと書いておくだけ。

  • 1点だけ愚痴。HugoのREADME.mdに "Disable logging temporarily by adding the following:" との記載あり。

Build.gradle
hugo { enabled false } 
  • ふむふむ。。。と思って記載したらビルドエラー。
  • しばらく悩んだ後にGoogle先生に問い合わせたら、でるわでるわ。。。READMEには記載したけど未実装とのこと。
  • 未実装の機能を READMEに書くな!(せめて注釈つけてよ。。。)

まとめ

  • 「多重起動時に後発Dialogの表示を抑制」「呼び出し元Fragmentにコールバック可」機能を持ったBaseDialogFragmentを実装しました。
  • 幾度となく行われた車輪の再発明ですが、実際に実装してみると勉強になります。
8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?