お断り:今回の記事は行き過ぎた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#showDialog、Activity#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しないと変数の値が変わる可能性があります!!もちろん、Collection
やCalendar
などもともと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
とか)を実装します。
はたして読みやすいコードを生産できているのでしょうかね?難しいところです。