人生ずっと勉強

人生ずっと勉強ですね。 https://twitter.com/KiyotakaGoto

View のない non-UI ( non-graphical ) fragment の1つの使い道

fragment に関する公式ドキュメントを読んでると、UI を持たない fragment を作ることもできるとあるけれど、一体何に使うのかよくわかりませんでした。
ところが最近、会社の先輩からその1つの使い道を教えてもらったので、忘れないようにメモ。

DialogFragment の Helper クラスとして使う

たとえば、以下の様な要件があるときに、non-UI fragment が使えます。

  • ネット上の画像を取得して、DialogFragment の layout にその画像を表示させたい
  • Activity に LoaderCallbacks を implements させたくない

このとき、以下のように素直に DialogFragment を継承したクラスに LoaderCallbacks を implements させて AsyncTaskLoader を呼びだそうとすると、
「IllegalStateException: Fragment not attached to Activity」が発生します。

public class SampleDialogFragment extends DialogFragment implements LoaderCallbacks<Bitmap>{
    private FragmentManager mManager;
    private String mTag;
    private Bitmap mImageData;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View dialogView = inflater.inflate(R.layout.dialog_fragment_layout, container, false);

        ImageView imageView = (ImageView) dialogView.findViewById( R.id.image );
        imageView.setImageBitmap( mImageData );
        return dialogView;
    }

    // Activity からはこのメソッドを呼び出す
    public void showDialog ( FragmentManager manager, String tag ) {
        mManager = manager;
        mTag = tag;
        getLoaderManager().initLoader(0, null, this );
    }

    @Override
    public Loader<Bitmap> onCreateLoader(int arg0, Bundle arg1) {
        return new SomeAsyncTaskLoader( getActivity() );
    }

    @Override
    public void onLoadFinished(Loader<Bitmap> arg0, Bitmap imageData) {
        mImageData = imageData;
        show( mManager, mTag );
    }

    @Override
    public void onLoaderReset(Loader<Bitmap> arg0) {}
}

LoaderManager は Activity/Fragment のライフサイクルと連動して動くみたいなんですが、
上記の showDialog () を呼んだ時点ではまだこの fragment 自体が activity に紐付いておらず、activity のライフサイクルと連動していないので、結果としてLoaderManager が連動するライフサイクルが定まらないためにこの Exception が投げられるのかなと思います。

これを回避するために、UI を持たないフラグメントをただのインタフェースとして用意し、Activity にそのフラグメントを追加し、Activity からはそのフラグメントが提供するメソッドを介して DialogFragment を表示させるようにします。
おおまかな手順は、

  • non-UI fragment を用意し、LoaderCallbacks をこれに implements する。
  • Activity で non-UI fragment を add する
  • Activity から non-UI fragement が提供するメソッドが呼び出されたら、そのメソッド内で loader を起動する
  • onLoadFinished() で 実際の DialogFragment を呼び出す

という感じです。

しかし、まだ問題があります。

Can not perform this action inside of onLoadFinished

たとえば素直に onLoadFinished() 内で DialogFramgnet のインスタンスを生成し、表示させようとします。

    @Override
    public void onLoadFinished(Loader<Bitmap> arg0, Bitmap imageData) {
        SampleDialogFragment dialog = new SampleDialogFragment();
        dialog.show( getFragmentManager(), TAG, imageData );
    }

すると以下の様な exception が投げられます。

FATAL EXCEPTION: main
java.lang.IllegalStateException: Can not perform this action inside of onLoadFinished

これについては、以下の stack over flow あたりを参考に回避します。
android - This Handler class should be static or leaks might occur: IncomingHandler - Stack Overflow

もろもろまとめると

サンプルプロジェクトを以下に置いてみました。
kiyotakagoto/NonUIFragmentSample · GitHub