読者です 読者をやめる 読者になる 読者になる

@numa08 猫耳帽子の女の子

明日目が覚めたら俺達の業界が夢のような世界になっているとイイナ。

Fragment時代のDialog管理

お断り:今回の記事は行き過ぎたfinal病患者によって記述された結構アレなサンプルです。

Fragment時代になっていた

AndroidにFragmentが登場してすでに2年以上経ちました。

Android3.0以上で利用可能なAPIとして登場したFragmentもSupport LibraryによってAndroid2.3以下のOSでも利用可能となり、

最近新たに実装されるアプリはほぼ間違いなくFragmentを利用しているのではないでしょうか。

AndroidとDialog

UIデザインの一つとして、Dialogが欠かせないものである点は今も昔も変わりません。

画面遷移を行うこと無くユーザに入力を促したり、あるいは注意を表示したりと様々な局面で利用されています。

このDialogもFragmentの登場以前と以後とで管理方法が大きく異る物の一つです。

今回はその中でもProgressDialogに焦点を絞ります。

設計としては、onCreateでProgressDialogを表示した後、適当なタイミングで非同期処理が走り非同期処理終了時に

someCallbackが呼び出され、ProgressDialogを閉じると言うものです。

Fragment以前のDialog

Activity#showDialog

Dialogを管理するある一つの方法がActivity#showDialogActivity#onCreateDialogそしてActivity#dismissDialogを利用するもの。

int型の変数を識別子にしてDialogの作成、表示、消去を実装していました。次のような感じで自分は利用していました。

private static final int sID_DIALOG_PROGRESS = 201307011;
private final SparseArray mDialogArray = new SparseArray<Dialog>();

@override
protected void onCreate(Bundle savedInstanceState){
    {
        final Dialog progressDialog = new ProgressDialog(this);
        progressDialog.setMessage("Working...");
        progressDialog.setCancelable(false);
                                    
        mDialogArray.put(sID_DIALOG_PROGRESS, progressDialog);
    }       
    showDialog(sID_DIALOG_PROGRESS);
}

@override
protected Dialog onCreateDialog (int id, Bundle args){
    return mDialogArray.get(id);
}

@override
public void someCallBack(){
    dismissDialog(sID_DIALOG_PROGRESS);
}

Fragment時代のDialog

DialogFragment#show

さて時間は経過し今はFragment全盛期。DialogもFragmentManagerによって管理する方法が推奨されるようになり、Activity#showDialogやActivity#dimissDialogはAPI lvel13(Android4.0)以降から非推奨となりました。

その代わりにDialogFragment#showDialogを利用するようになりました。

そういった理由からDialogの表示方法はだいたいこんな感じでしょうか

private DialogFragment mProgressDialog;

@override
protected void onCreate(Bundle savedInstanceState){
    mProgressDialog = new MyProgressDialog();
    mProgressDialog.show(getFragmentManager(), null);
}

@override
public void someCallback(){
    mProgressDialog.dismiss();
}

あれ?finalしてないよ・・・?

そうです、finalしてません!大変です!!

フィールド変数をfinalしてないなんてすっごい危険です!!

finalしないと変数の値が変わる可能性があります!!もちろん、CollectionCalendarなどもともとmutableなAPIは沢山あります。

ありますが、それでもfinalしたい!finalしたい!!したいぞ!!!

無理にでもfinalしたい

DialogFragment#showの第二引数は、FragmentManager#findFragmentByTagに利用されるtagとなります。そこで上記のコードを次のように修正します。

private static final String sTAG_MY_PROGRESSDIALOG = "my_progress_dialog";

@override
protected void onCreate(Bundle savedInstanceState){
    final mProgressDialog = new MyProgressDialog();
    mProgressDialog.show(getFragmentManager(), sTAG_MY_PROGRESSDIALOG);
}

@override
public void someCallback(){
    final DialogFragment dialog = (DialogFragment)getFragmentManager().findFragmentByTag(sTAG_MY_PROGRESSDIALOG);
    if(dialog == null){
        reutnr;
    }
    dialog.dismiss();
}

よかった。無事に全ての変数をfinal宣言することができました。

でもちょっと待ってください、実はこのコードには問題があります。

FragmentManager#findFragmentByTagの説明文によると、どうもActivityによって追加されたFragmentを探し、見つからなければ現在のbackstackにを探すと書いてあります。

なお、どうしても見つからないければnullが帰ります。

さて、FragmentFragmentManagerの管理下に置かれためにはFragmentがattachされている必要があります。

逆に言うと、attachされる前にFragmentManager#findFragmentByTagを呼び出してもお目当てのFragmentは得られません。

そこでshowDialogです!!

そんな訳でどうしても全ての変数をfinal宣言したかったボクがたどり着いた先のコードが次です

private final Map<String, DialogFragment> mShownDialogMap = new HashMap<String, DIalogFragment>();
private final Map<String, Class<? extends DialogFragment>> mDialogMap = new HashMap<String, Class<? extends DialogFragment>>();

private void showDialog(String tag, Bundle args){
    final FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
    final Fragment prevDiag = getFragmentManager().findFragmentByTag(tag);
    if(prevDiag != null){
        transaction.remove(prevDiag);
    }
    transaction.addBackStack(null);
    DialogFragment dialog;
    if(bundle == null){
        dialog = (DialogFragment)Fragment.instantiate(this, mDialogMap.get(tag).getName());
    } else {
        dialog = (DialogFragment)Fragment.instantiate(this, mDialogMap.get(tag).getName(), args);
    }
    if(dialog == null){
        return;
    }
    mShownDialogMap.put(tag, dialog);
    dialog.show(getFragmentManager(), tag);
}

private void dismissDialog(String tag){
    DialogFragment dialog = (DialogFragment)getFragmentManager().findFragmentByTag(tag);
    if(dialog == null){
        dialog = mShownDialogMap.get(tag);
    }
    if(dialog == null){
        return;
    }
    dialog.dismiss();
}

private static final String sTAG_MY_PROGRESSDIALOG = "my_progress_dialog";
@override
protected void onCreate(Bundle savedInstanceState){
    mDialogMap.add(sTAG_MY_PROGRESSDIALOG, MyProgressDialog.class);
}

@override
public void someCallback(){
    dismissDialog(sTAG_MY_PROGRESSDIALOG);
}

やった!やったぞ!!メソッド内の変数以外全部final宣言できたぞ!!やったあああああああああああ!!

終わりに

最近本気でfinal宣言してないと悲しくてつらいです。

一時期PNDと言う静的解析ツールを利用していたのですが、デフォルト設定だと、

final宣言できるのにfinalしてない変数があると警告を出すんですよ。

気がつけば極力全ての変数にfinalをつけることが生きがいみたいになってきてしまい、と同時に変数のスコープを狭めることに喜びを感じるようになりました。

実際コードを書く際にも、全てのフィールド変数をfinal宣言しコンストラクタで設定した値を変えることはありません。当然setterメソッドなんて存在しませんし、getterも極力作らずにコードを書きます。

どうしてもオブジェクトの状態を変化させる場合は、専用のメソッド(changeValueとか)を実装します。

はたして読みやすいコードを生産できているのでしょうかね?難しいところです。