フラグメントで状態を保存する

フラグメントの状態は、Android システムのさまざまなオペレーションに影響されます。Android フレームワークは、ユーザーの状態が保存されることを保証するために、フラグメントとバックスタックを自動的に保存し、復元します。したがって、それと同様に、フラグメント内のデータも保存し、復元する必要があります。

下記の表は、フラグメントが状態を喪失する原因となるオペレーションと、オペレーションの変化に際してさまざまなタイプの状態が保持されるかどうかを示しています。この表に記載されている状態のタイプは次のとおりです。

  • Variable: フラグメント内のローカル変数。
  • ViewState: フラグメント内で 1 つ以上のビューによって所有されるすべてのデータ。
  • SavedState: onSaveInstanceState() に保存する必要がある、このフラグメント インスタンスに固有のデータ。
  • NonConfig: サーバーやローカル リポジトリなどの外部ソースから取得されたデータ、またはコミット後にサーバーに送信されたユーザー作成データ。

多くの場合、Variable は SavedState と同様に処理されますが、次の表では、それぞれに対する各種のオペレーションの影響を明示するために、この 2 つを別々に記載しています。

オペレーション Variables ViewState SavedState NonConfig
バックスタックに追加 x
構成の変更 x
プロセスの終了 / 再作成 × ○*
バックスタックに追加せずに削除 × × × ×
ホストの終了 x × × x

* ViewModel の保存済み状態モジュールを使用すると、プロセスが終了しても NonConfig 状態を保持できます。

表 1: フラグメントに対する各種の破壊的オペレーションとそれらがさまざまなタイプの状態に及ぼす影響

具体的な例を見てみましょう。ランダムな文字列を生成して TextView に表示し、友だちに送信する前に文字列を編集するオプションを表示する画面があるとします。

さまざまなタイプの状態を示すランダム テキスト生成アプリ
図 1. さまざまなタイプの状態を示すランダム テキスト生成アプリ

この例では、ユーザーが編集ボタンを押すと、アプリは EditText ビューを表示します。ユーザーはこのビューでメッセージを編集できます。ユーザーが [CANCEL] をクリックすると、EditText ビューがクリアされ、表示設定が View.GONE に設定されます。この種の画面では、シームレスなエクスペリエンスを保証するために、次の 4 つのデータの管理が必要になる場合があります。

データ タイプ 状態タイプ 説明
seed Long NonConfig 新しい良好な結果をランダムに生成するために使用するシード。ViewModel が作成されるときに生成されます。
randomGoodDeed String SavedState + Variable フラグメントが初めて作成されるときに生成されます。randomGoodDeed は、プロセスの終了と再作成の後でも同じランダムで良好な結果がユーザーに表示されることを保証するために、保存されます。
isEditing Boolean SavedState + Variable ユーザーが編集を開始したときに true に設定されるブール値のフラグ。isEditing は、フラグメントが再作成されたときに画面の編集領域が引き続き表示されることを保証するために、保存されます。
編集済みのテキスト Editable ViewState(EditText が所有) EditText ビューで編集されたテキスト。EditText ビューには、ユーザーの進行中の変更を失わないようこのテキストが保存されます。

表 2: ランダム テキスト生成アプリが管理する必要がある状態

以下のセクションでは、破壊的オペレーションに抗してデータの状態を適切に管理する方法について説明します。

ViewState

ビューは、それ自身の状態を管理する役割を担います。たとえば、ビューがユーザー入力を受け入れたとき、構成の変更を処理するためにその入力を保存および復元するのは、ビューの役割です。Android フレームワークが提供するビューは onSaveInstanceState()onRestoreInstanceState() の固有の実装を備えているので、フラグメント内でビューの状態を管理する必要はありません。

たとえば、上記のシナリオでは、編集済みの文字列は EditText 内で保持されます。EditText は、表示するテキストの値だけでなく、その他の詳細情報(選択されたテキストの開始位置と終了位置など)も認識します。

ビューは、自身の状態を保持するために ID を必要とします。この ID は、フラグメントとそのビュー階層内で一意であることが必要です。ID のないビューは自身の状態を保持できません。

<EditText
    android:id="@+id/good_deed_edit_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

表 1 に示すように、フラグメントの削除またはホストの破棄を行わないすべてのオペレーションに抗して、ビューは ViewState を保存し、復元します。

SavedState

フラグメントは、フラグメントの動作に必要不可欠な少数の動的状態を管理する役割を担います。Fragment.onSaveInstanceState(Bundle) を使用すると、簡易シリアル化されたデータを保持できます。Activity.onSaveInstanceState(Bundle) と同様に、バンドルに配置したデータは、構成の変更とプロセスの終了 / 再作成に抗して保持され、フラグメントの onCreate(Bundle) メソッド、onCreateView(LayoutInflater, ViewGroup, Bundle) メソッド、onViewCreated(View, Bundle) メソッドの中で使用できます。

上記の例では、randomGoodDeed はユーザーに表示される結果であり、isEditing はフラグメントが EditText を表示するか非表示にするかを指定するフラグです。この保存済み状態は、次の例に示すように、onSaveInstanceState(Bundle) を使用して保持する必要があります。

Kotlin

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putBoolean(IS_EDITING_KEY, isEditing)
    outState.putString(RANDOM_GOOD_DEED_KEY, randomGoodDeed)
}

Java

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putBoolean(IS_EDITING_KEY, isEditing);
    outState.putString(RANDOM_GOOD_DEED_KEY, randomGoodDeed);
}

onCreate(Bundle) で状態を復元するには、保存済みの値をバンドルから取得します。

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    isEditing = savedInstanceState?.getBoolean(IS_EDITING_KEY, false)
    randomGoodDeed = savedInstanceState?.getString(RANDOM_GOOD_DEED_KEY)
            ?: viewModel.generateRandomGoodDeed()
}

Java

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState != null) {
        isEditing = savedInstanceState.getBoolean(IS_EDITING_KEY, false);
        randomGoodDeed = savedInstanceState.getString(RANDOM_GOOD_DEED_KEY);
    } else {
        randomGoodDeed = viewModel.generateRandomGoodDeed();
    }
}

表 1 に示されているように、フラグメントがバックスタックに追加されるとき、変数は保持されます。そうした変数を保存済み状態として扱うと、どのような破壊的オペレーションがあってもそれらを保持できます。

NonConfig

NonConfig のデータは、フラグメントの外部(たとえば ViewModel 内)に配置する必要があります。上記の例では、seed(NonConfig の状態)は ViewModel 内で生成されます。状態を保持するためのロジックは、ViewModel が所有しています。

Kotlin

public class RandomGoodDeedViewModel : ViewModel() {
    private val seed = ... // Generate the seed

    private fun generateRandomGoodDeed(): String {
        val goodDeed = ... // Generate a random good deed using the seed
        return goodDeed
    }
}

Java

public class RandomGoodDeedViewModel extends ViewModel {
    private Long seed = ... // Generate the seed

    private String generateRandomGoodDeed() {
        String goodDeed = ... // Generate a random good deed using the seed
        return goodDeed;
    }
}

ViewModel クラスは、その性質上、画面の回転などの構成の変更に抗してデータを保持することを可能にします。また、フラグメントがバックスタックに配置されたとき、このクラスはメモリ内にとどまります。プロセスの終了と再作成の後は、ViewModel が再作成され、新しい seed が生成されます。SavedState モジュールを ViewModel に追加すると、ViewModel はプロセスの終了と再作成に抗して単純な状態を保持できるようになります。

参考情報

フラグメントの状態の管理の詳細については、以下のリソースをご覧ください。

Codelab

ガイド