Fragmentの設置について私がやってるベストプラクティス

  • 31
    いいね
  • 0
    コメント

先日、@kiriminさんからこんなツイートがありました。

というわけで、雑に煽ってみたら記事を書いていただけました :raised_hands:
ありがとうございました :pray::pray::pray::pray::pray:

実はこの内容、4年くらい前からベストプラクティスとしてやっていました。特にアウトプットしたことがなかった(たぶん)ので、この機会に知っていることを吐き出しておこうと思います。

※この記事は上記記事への蛇足が中心なので、上記の記事を先にお読みください。

TL;DR

  • Activityの扱いを踏まえると、Fragmentの初期配置は savedInstanceState == null のときにやるほかないです
  • savedInstanceStateで状態を保存しとくと何かと楽ですよ
  • 筆者はaddよりreplaceのほうが好きです
  • もっと知りたい人はMaster of Fragment読んで

何が起こっているか

次の2文が核心を突いていたと思います。

Activityがリストアされる度にAcrtivityのonCreateが呼ばれるため
onSavedInstanceStateがnullじゃない場合にはaddを行わない

実は、ActivityはFragmentとsavedInstanceStateについて、かなり密接な扱いをしています。SDK側のActivityの実装を見てみると、Fragmentを次のように扱っています。

Activity.java
protected void onCreate(@Nullable Bundle savedInstanceState) {
    /* 略 */
    if (savedInstanceState != null) {
        Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
        mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.fragments : null);
    }
    /* 略 */
}

protected void onSaveInstanceState(Bundle outState) {
    outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
    Parcelable p = mFragments.saveAllState();
    if (p != null) {
        outState.putParcelable(FRAGMENTS_TAG, p);
    }
    getApplication().dispatchActivitySaveInstanceState(this, outState);
}

メモリ枯渇("Activityを保持しない"で再現できるのはこれ)や画面回転などによりActivityが破棄されるときに呼ばれる onSaveInstanceState の中で FRAGMENTS_TAG という名前でFragmentが保存されています。それが、 savedInstanceState != null のとき、つまり再生成後のonCreateで復元されています。

そう、 ActivityはsavedInstanceStateでViewの状態と同じようにFragmentそのものも復元するのですsuper.onCreate を実行した時点で、破棄される前に配置されていたFragmentがそのまま再配置されているわけなので、そこに新たにFragmentをaddすれば、もちろん積まれることになります。

きりみんさんの記事でreplaceを使ったパターンのときにFragmentが2回生成されているのも、Activityのsuper.onCreateで再生成された分と、その後でreplaceにより生成された分だと思われます。

どうすればよいか

やはり savedInstanceState == null をチェックして、初回のonCreateのときだけFragmentをaddする方法が、筆者にとってもベストプラクティスです。まさしくこれ↓ですね。

if (savedInstanceState == null) {
    getSupportFragmentManager()
            .beginTransaction()
            .add(R.id.container, new MainFragment())
            .commit();
}

少し観点を変えると、Fragmentの扱いをActivityのsavedInstanceStateの仕組みに委ねるということでもあります。Fragmentの中で各種の状態をしっかりとsavedInstanceStateの管理下に置いていれば、それらも復元されるということです。

上手く使えば画面回転などの際に考えることが減るので、積極的に使っていきたいところです。

おまけ: addとreplaceの使い分け

実は筆者はFragmentの配置にaddを使うことがほとんどありません。バックスタックを有効に使おうとしているときでない限りは、現在どのFragmentが積まれているのかという状態を考えたくないのです。

一方、replaceは気楽です。ひとつのレイアウトにひとつのFragmentしかattachされていないことを保証できます。

そんなわけで、筆者は基本的にreplaceを使うことにしており、余程バックスタックを使いたくなったときだけaddを使っています。経験上、そのほうがトラブルが少ないと感じています。

まとめ

というわけできりみんさんの記事に蛇足を付け足してみました。Fragmentは普通に使うだけでもこういう罠が随所に隠れているので、注意が必要ですね。

Fragmentに関する知見はあんざいゆきさんのMaster of Fragmentにとてもよくまとまっているので、Fragmentと仲良くしていきたい方は是非ご覧ください。