rememberはJetpack Composeの関数で、再コンポーズ間で値を保持し、Composable関数の再起動ごとに状態がリセットされるのを防ぎます。rememberがない場合、関数内で宣言されたすべての変数はUIが更新されるたびにリセットされ、状態が不安定で役に立たなくなります。Google Android Developers, 2026によると、rememberの正しい使用は宣言的UIにおける適切な状態管理の基礎です。
重要なポイント
rememberはandroidx.compose.runtimeパッケージのJetpack Compose組み込み関数で、Composable関数の連続した呼び出し間でその値を保持するオブジェクトを作成します。技術的には、rememberはスロット(UIツリーの各関数呼び出し位置に割り当てられた特別なメモリセル)で動作します。関数がコンポジション内に残っている限り(if条件で削除されていない限り)、rememberは各再コンポーズで保存された値を返します。
rememberの構文はシンプルです。中括弧内に初期値の計算ブロックを指定します。ブロックは関数の最初のコンポジション時に一度だけ実行されます。後続のすべての再コンポーズはブロックを再実行せずに保存済みの値を返します。ただし、キーパラメータが指定されている場合、キーが変更されるとブロックが再実行され、入力データに基づいて値を再計算できます。
rememberはKotlinの魔法ではありません。キーパラメータと計算ラムダを持つ単純な関数です。内部実装ではComposerを使用してスロットの読み書きを行います。Kotlinコンパイラはremember呼び出しを見ると、現在のCompositionContextにアクセスしてスロットを管理するコードを生成します。つまり、rememberはComposable関数内またはコンポジションコンテキスト内の別の関数内でのみ呼び出すことができます。
Jetpack Composeにはrememberの3つの主要なバリエーションがあり、それぞれ目的が異なります。基本的なrememberは現在のプロセスメモリにのみ値を保持します。画面回転(設定変更)時にComposeはコンポジションを再作成し、すべてのremember値がリセットされます。設定変更やプロセス停止時にデータを保持するにはrememberSaveableを使用します。
rememberSaveableはSavedStateHandleまたはParcelableメカニズムを介して値をBundleにシリアライズします。これにより、画面回転、「アクティビティを保持しない」設定、さらには一時的なアプリ最小化からも値を保持できます。ただし、rememberSaveableは保存できるデータの種類に制限があります。プリミティブ、Parcelable、Serializable、またはSaverコンバーターをサポートする必要があります。
derivedStateOfは保持メカニズムではなく、最適化です。他のStateオブジェクトから値を計算するStateを作成します。derivedStateOfはソース状態の変更に反応しますが、購読者がいる場合にのみ値を再計算します。現在の再コンポーズがderivedStateOfを読み取らない場合、計算は実行されず、頻繁だが不要な更新でリソースを節約します。
@Composable
fun RememberVariants() {
// 1. 基本的なremember: コンポジションを離れるまで保持
val createdAt = remember { System.currentTimeMillis() }
// 2. rememberSaveable: 画面回転に耐える
var username by rememberSaveable { mutableStateOf("") }
// 3. derivedStateOf: 必要なときにのみ計算
val isButtonVisible = remember {
derivedStateOf { username.length() > 3 }
}
Text("作成日: $createdAt")
TextField(value = username, onValueChange = { username = it })
if (isButtonVisible.value) {
Text("ボタンが表示されます")
}
}
rememberとrememberSaveableの選択は、設定変更中にデータを保持することがどの程度重要かによって決まります。回転時に失っても構わない一時的な状態(アニメーション、現在のスクロール位置、フォーカス状態)には通常のrememberで十分です。重要なデータ(フォームのテキスト、選択されたアイテム、チェックボックス)にはrememberSaveableを使用してください。
パフォーマンスはrememberSaveableの方が通常のrememberよりも低くなります。Bundleへのシリアライズが必要だからです。rememberSaveableは本当にActivityの再作成を生き残る必要があるデータにのみ使用してください。それ以外は単純なrememberで十分です。rememberSaveableの過剰使用は、画面回転時やアプリ切り替え時の速度低下を引き起こします。
ParcelableやSerializableをサポートしないクラスを扱う場合は、Saver(オブジェクトの保存と復元方法を定義するコンバーター)を使用してください。Saverはsave(オブジェクトを保存可能な型に変換)とrestore(保存されたデータからオブジェクトを復元)のラムダのペアで記述されます。mutableStateListOfとmutableStateMapOfには標準のSaverがすでに実装されています。
| 特性 | remember | rememberSaveable |
|---|---|---|
| 再コンポーズ時の保持 | はい | はい |
| 回転時の保持 | いいえ | はい |
| プロセス停止時の保持 | いいえ | はい |
| 型の要件 | 任意 | Parcelable、Serializable、Saver |
| パフォーマンス | 高い | 中程度 |
典型的なシナリオを考えてみましょう。プロフィール編集画面でrememberを複数の目的で使用します。フォームフィールドの状態の保存、派生値の計算、計算コストの高い操作のキャッシュです。
data class ProfileUiState(
val name: String = "",
val bio: String = "",
val isSaving: Boolean = false
)
@Composable
fun ProfileEditor() {
var state by rememberSaveable { mutableStateOf(ProfileUiState()) }
val isValid = remember {
derivedStateOf { state.name.isNotBlank() && state.bio.length() <= 500 }
}
val bioWarning = remember(state.bio) {
if (state.bio.length() > 400) {
"${500 - state.bio.length} chars left"
} else null
}
Column(modifier = Modifier.padding(16.dp)) {
OutlinedTextField(
value = state.name,
onValueChange = { state = state.copy(name = it) },
label = { Text("名前") }
)
OutlinedTextField(
value = state.bio,
onValueChange = { state = state.copy(bio = it) },
label = { Text("概要") }
)
bioWarning?.let { Text(it, color = MaterialTheme.colorScheme.error) }
Button(onClick = { /* save */ },
enabled = isValid.value) {
Text("保存")
}
}
}
この例では、状態はrememberSaveableを介して保存されます。画面回転時にテキストは失われません。isValidはderivedStateOfを介して計算され、再コンポーズ中の不要な計算を防ぎます。bioWarningはbioキー付きのrememberを使用します。これはコストの高い計算(デモ用、オプション)で、すべての再コンポーズではなくbioが変更されたときにのみ再計算されます。
派生状態とは、他のStateオブジェクトから計算される値です。毎回の再コンポーズで計算して同一結果にCPUを浪費する代わりに、rememberとderivedStateOfはソースが変更されたときにのみ値を計算します。これはフィルタリング、ソート、データ集計に特に便利です。
キー付きremember(remember(key) { calculation })は別の最適化メカニズムです。前回の再コンポーズからキーが変更されていなければ、計算ブロックは実行されず、キャッシュされた値が返されます。これは作成にコストがかかるオブジェクト(日付フォーマット、JSON解析、大きな不変コレクションの作成など)のキャッシュに便利です。
@Composable
fun SearchResults(allItems: List<Item>, query: String) {
// derivedStateOf: フィルターは入力が変更されたときにのみ再計算
val filtered = remember {
derivedStateOf {
allItems.filter { it.title.contains(query, true) }
}
}
// キー付きremember: フォーマットされた統計はクエリ変更時にのみ再計算
val statsText = remember(query) {
"Results for query \"$query\": ${filtered.value.size}"
}
Text(statsText)
LazyColumn {
items(filtered.value, key = { it.id }) { item ->
Text(item.title)
}
}
}
filteredはderivedStateOfで、allItemsまたはqueryが変更されると自動的に再計算されます。statsTextはremember(query)を使用します。コストの高い文字列フォーマットは、検索クエリが変更されたときにのみ実行されます。derivedStateOfとキー付きrememberの組み合わせは最大のパフォーマンスを提供します。派生状態は必要なときにのみ計算され、複雑なオブジェクトはキーが変更されるまでキャッシュされます。
よくある質問
いいえ、rememberはcompose.runtimeパッケージの関数で、CompositionContextが必要です。@Composable関数内、またはComposableから呼び出された別の関数内でのみ呼び出すことができます。コンポジション外でデータを保存するにはViewModelを使用してください。
キーがない場合、rememberは計算ブロックを最初のコンポジション時に一度だけ実行します。後続のすべての再コンポーズは保存された値を返します。データが変更されたときに値を再計算する必要がある場合は、必ずキーとして指定してください: remember(data) { compute(data) }。
rememberを直接リセットする方法はありません。唯一の方法は、関数をコンポジションから削除し(たとえばif条件で非表示にする)、再度表示することです。再入力時にrememberブロックが再実行され、新しい初期値が作成されます。
rememberはComposable関数のスロットに状態を保存し、関数がコンポジション内にある間だけ存続します。ViewModelは画面のライフサイクルが続く限り存続します。ViewModelは回転時に保持され、ビジネスロジックに使用されます。rememberは単一の関数の外部では不要なローカルUI状態のためのものです。
はい、rememberは@Preview Composable関数で正しく動作します。Previewは完全なCompositionContextを作成するためです。ただし、rememberSaveableはPreviewでは正しく動作しない可能性があります。プレビュー環境ではSavedStateHandleが存在しない場合があるためです。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。