84
67

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-08-12

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

84
67
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
84
67

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?