LoginSignup
24

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-11-14

結論

  • DialogFragment#show(manager, tag)をoverrideしてフラグをオンにし、DialogFrament#onResume()でオフにしろ
    • ただしダイアログ表示失敗に気をつけろ

問題:DialogFragmentが二重表示されてしまう

Androidにおいてダイアログというのは、周知のとおりDialogを直接呼ぶのではなく(これは古いやりかた)、DialogFragmentを使って表示させる必要がある。
ところがこれはFragmentManagerを経由するので遅い。遅いというか、UIをロックしない。だから、ダイアログを表示しおわるまでのあいだにUI操作ができてしまう。

static String tag = "TAG"
private String settingValue = "sample";

public void onButtonClick(View v) {
  MyDialogFragment f = MyDialogFragment.newInstance(settingValue); 
  f.show(getSupportFragmentManager, tag);
}

こんなことしていると、ボタン連打でMyDialogFragmentが大量に表示できてしまう。同じタグをちゃんと設定しているにもかかわらずだ。

対応Step1: タグをもとにほかのFragmentを消す

連打されても、同じタグのDialogFragmentは1つしか出したくないだろう。だから表示前にチェックして、同じタグのものは消しておくことにしよう。

public static void removeFragmet(FragmentManager manager, String tag) {
  if (manager == null) return;
  Fragment prev = manager.findFragmentByTag(tag);
  if (prev == null) return;
  if (prev instanceOf DialogFragment) {
    Dialog dialog = prev.getDialog();
    if (dialog != null && dialog.isShowing()) {
      dialog.dismiss();
      prev.onDismiss(dialog);
    }
  }
  val ft = manager.beginTransaction();
  ft.remove(prev);
  ft.commitAllowingStateLoss();
  manager.executependingTransactions();
}

参考ページ

これはなかなかいいが、問題の解決にはなっていない。生成されたFragmentFragmentManagerに登録されるまでのあいだ、FragmentManager#findFragmentByTag(tag)は何も返却しない。そのあいだに連打が入れば、そいつらはスルーされてしまう。

対応Step2:表示準備中フラグを立てる

これでは困るので、いまDialogFragmentを準備中である、というフラグを立てることにしよう。準備が終わったらフラグをオフにする。準備が終わったら、というのは、ダイアログによってUIがロックされ、このフラグに頼らなくても、ダイアログを表示させるボタンが操作できなくなったとき、ということだ。

abstract class GenericDialogFragment: AppCompatDialogFragment {
  public static AtomicBoolean isSomeDialogShowing = AtomicBoolean(false);

  @Override
  public void show(FragmentManager manager, String tag) {
    if (isSomeDialogShowing.getAndSet(true)) return;
    super.show(manager, tag);
  }

  @Override
  public void onResume() {
    super.onResume();
    isSomeDialogShowing.set(false);
  }
}

あとはすべてのDialogFragmentを、これを継承して実装すればよさそうだ。
なお、バックグラウンドでの通信エラー発生時など、緊急でユーザ操作を求めたい場合は、このGenericDialogFragment#isSomeDialogShowingフラグを念のため解除してから、当該の緊急ダイアログを表示させてやる必要がある。

参考ページ

対応Step3. 表示失敗時をケアする

……ところで、DialogFragment#show(manager, tag)は、IllegalStateExceptionを吐いて失敗することがある。属するActivity#onSavedInstanceState()が呼ばれたあとで呼ばれてしまった場合などだ。
一度でもそれが発生すると、ダイアログの#onResume()までたどりつけないので、上記のGenericDialogFragment#isSomeDialogShowingフラグはtrueになったまま、二度と戻ることがない。つまり、ほかのすべてのDialogFragmentは、フラグによって排除されて、表示されなくなる。
そこで、

  @Override
  public void show(FragmentManager manager, String tag) {
    if (isSomeDialogShowing.getAndSet(true)) return;
    try {
      super.show(manager, tag);
    } catch (Exception e) {
      isSomeDialogShowing.set(false);
    }
  }

のように気をつかってやろう。

対応Step4. それでも……

ところが、いろんな画面でダイアログを出しまくっていると、これでもたまにGenericDialogFragment#isSomeDialogShowingフラグがtrueになったままになることがある。
そこで、Activity#onCreate(savedInstanceState)Activity#onActivityResult(requestCode, resultCode, data)など、ダイアログを出していないことがはっきりしているライフサイクルのタイミングで、GenericDialogFragment.isSomeDialogShowing.set(false)をセットしてやるほうがいいかもしれない。

そもそもそんなUIはやめたほうがいいかも

しかし、そもそもの問題として、ボタンを押すたびにダイアログを出すというUIをがんばって実現すべきなのか、というのは疑わしい。ダイアログというのはユーザの操作の選択肢を奪って中断を強いるもので、UIとしてそんなにいいものではない。アラートをいっぱい出すWebページはブラクラとして忌み嫌われる。
緊急の対応を求めるならともかく(退会の確認とか、ユーザによる不正アクセスを検知したとか)、ユーザの操作の確認を求める程度のことであれば、確認ダイアログを挟むよりも、処理を受け付けつつSnackbarで取り消しを可能にするなどのほうがよいかもしれない。DatePickerみたいな複雑なUIも、ダイアログで処理するのはよろしくない。そもそも複雑なものを頻繁にユーザに入力させるべきではなく、デフォルトを設定しておくべきで、それを修正する場合は画面遷移にして、一手間遠いところに埋め込んでおく、というほうがいい気がする。
Material design guidelineは、どんなときダイアログを使うべきかを示している。その時点のコンテクストを妨げるものであるから、控えめに使え、というのが趣旨だ。ほんとうにダイアログでなければダメなのか、画面をロックせず(たとえば)ネットワーク通信の結果をあとで反映するだけにできないか、というのを考えなおすべきだろう。

参考:Dialogs - Components - Material design guidelines

Use dialogs sparingly because they are interruptive. Their sudden appearance forces users to stop their current task and focus on the dialog content. Not every choice, setting, or detail warrants interruption. Alternatives to dialogs include menus or inline expansion, both of which maintain the current context.

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
24