ボタン連打で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程度かかる)
- ダイアログ表示中かどうかをそもそもチェックしていない。
でした。
絶対コレ悩んでるのは自分だけではないはず
- 参考とさせて頂いた記事
-
@hackugyoさんのボタン連打でDialogFragmentを二重表示させない
- そのものズバリな記事。
-
@kojionilkさんのDialogFragment 実装パターン
- DialogFragmentからFragmentへのコールバック方法を参考にさせていただきました。
-
@hackugyoさんのボタン連打でDialogFragmentを二重表示させない
BaseDialogFragmentの実装 (extendsすれば重複が防げるモノ)
- やりたいこと
-
表示元のFragmentからDialogFragmentに対して引数を渡せること。 -
DialogFragmentが正常終了(AlertDialogで各ボタンが押下)またはキャンセル(バック、ダイアログ外タップ)された場合、表示元のFragmentに対してonActivityResult()を利用してコールバックできること。 - コールバック用の
ListenerをFragmentに持たせないこと! -
DialogFragmentをshoworshowNowした際に、既に同一の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を実装しました。 - 幾度となく行われた車輪の再発明ですが、実際に実装してみると勉強になります。