Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

先日、@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と仲良くしていきたい方は是非ご覧ください。

Nkzn
新潟で農業アプリとか作ってる怪しいAndroidの人
https://blog.nkzn.info
water-cell
地球人口100億の時代への農業革命をWebテクノロジで支える
https://water-cell.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away