【Jetpack Compose】mutableStateOf(list) と mutableStateListOf() の違いと使い分け

Jetpack Composeでリストを扱う際、多くの開発者が最初にぶつかる壁が
「mutableStateOf(list) と mutableStateListOf() どっちを使えばいいの?」
という疑問です。

この記事では、両者の型、構造、そして「なぜあの書き方は動かないのか?」という落とし穴についてスッキリ整理して解説します。

 

🤔【結論】一目でわかる比較表

 

🤔 mutableStateOf(listOf<T>())

型の構造:箱ごと入れ替えるスタイル

mutableStateOf は「値(value)」そのものを監視します。リストを扱う場合、中身は不変(Immutable)な List であることが前提です。

型 :
MutableState<List<T>>

変更方法:
state.value = state.value + "A"

リストの中身をいじるのではなく、「新しいリストを作成して、Stateという箱に入れ直す」ことでComposeに通知します。

 

🤔 mutableStateListOf<T>()

型の構造:中身の動きを監視するスタイル

mutableStateListOf は、それ自体が MutableList のように振る舞い、内部の要素が追加・削除されたことを Compose に直接伝えます。

型 :
SnapshotStateList<T>

変更方法:
list.add("A"), list.remove("B")

リスト自体を再代入する必要はありません。addremove を実行するだけで、Compose が自動的に変更を検知して再描画してくれます。

 

🤔 実務での使い分け

mutableStateOf(list) を使うケース

主に UI State をデータクラスで一括管理する場合です。

・ Flow から combine して State を生成する
data class を用いた一方向データフロー(UDF)設計


data class MyUiState(
    val items: List<String> = emptyList()
)

// ViewModelなどで管理
var uiState by mutableStateOf(MyUiState())

mutableStateListOf() を使うケース

リストそのものが「動的な操作の主体」である場合です。

Navigation3rememberNavBackStack()
・ 要素の追加・削除が頻繁に発生する編集画面やスタック操作

 

🤔 まとめ

基本的には
「UI全体の状態管理なら mutableStateOf」
「リスト個別の動的操作なら mutableStateListOf」
と使い分けるのがスマートです。


Jetpack Compose Navigation3:rememberNavBackStack で実現する宣言的ナビゲーション

Jetpack Compose の次世代ナビゲーションライブラリ Navigation3 では、従来の NavHost と文字列ベースのルート定義から脱却し、より "Compose らしい" 状態管理へと進化しました。

その中核を担うのが rememberNavBackStack です。今回は、この新しい API の「型」の意味と、それを使った最小構成のサンプルコードを解説します。

 

🧑🏻‍💻 rememberNavBackStack の型とその正体

まず、この関数が何を返しているのかを確認しましょう。


val backStack: SnapshotStateList<T> = rememberNavBackStack(initialBackStack)

この関数が返す型は、Compose 独自の SnapshotStateList です。

 

🧑🏻‍💻 なぜこの型なのか?

オブザーバブル(監視可能): SnapshotStateList は、リストの中身(要素の追加や削除)が変化したことを Compose のランタイムに通知します。これにより、スタックを操作した瞬間に NavDisplay が自動的に再描画されます。

保存と復元: rememberNavBackStack は内部で rememberSaveable の仕組みを利用しています。つまり、画面回転やプロセスの再起動が発生しても、ナビゲーションの履歴(スタック)が消えずに保持されることを意味します。

直感的な List 操作: MutableList インターフェースを継承しているため、開発者は add()removeLast()clear() といった標準的なメソッドで遷移をコントロールできます。

 

🧑🏻‍💻 実装サンプル

1. 画面(デスティネーション)の定義
スタックに積むデータ型を sealed interface で定義します。


sealed interface Screen {
    data object Home : Screen
    data class Details(val id: String) : Screen
}

2. ナビゲーションの構築


@Composable
fun MyNavigationApp() {
    // initialBackStack で最初の画面を指定
    // 戻り値はスタックの状態を保持する SnapshotStateList<Screen>
    val backStack = rememberNavBackStack(initialBackStack = listOf(Screen.Home))

    NavDisplay(
        backstack = backStack,
        onBack = { 
            // スタックが2つ以上あれば、最後の要素を取り除いて「戻る」
            if (backStack.size > 1) {
                backStack.removeLast() 
            }
        }
    ) { screen ->
        when (screen) {
            is Screen.Home -> HomeScreen(
                onNavigateToDetails = { id -> 
                    // 1. 新しい画面をスタックに add するだけ
                    backStack.add(Screen.Details(id)) 
                }
            )
            is Screen.Details -> DetailsScreen(
                id = screen.id,
                onBack = { 
                    // 2. 現在の画面を remove するだけで戻れる
                    backStack.removeLast() 
                }
            )
        }
    }
}

 

🧑🏻‍💻 まとめ

Navigation3 において、rememberNavBackStack は単なる「履歴リスト」ではなく、「アプリの現在の状態そのもの」を管理するハブです。

型安全: 任意のオブジェクト(Screen)をスタックに積める。

宣言的: スタックの状態が変われば、UI(NavDisplay)が同期して変わる。

堅牢: SnapshotStateList により、構成変更(回転など)にも強い。

これまでの NavController による命令的な遷移から、「スタックというデータを操作する」という Compose 本来の作法へ。Navigation3 は開発体験を大きくシンプルにしてくれます。


Navigation3 時代の Destination 設計:sealed interface による型安全な実装パターンと使い分け

モダンな Android 開発において、Navigation はもはや単なる「画面の切り替え機」ではありません。

Destinationは、UIの状態やラベル、アイコンといったメタ情報を内包した、純粋な「型」として定義されるべきです。

ここでは、最新の Navigation ライブラリが目指す方向性に沿った、sealed interface による Destination 設計を提案します。

「シンプルさと拡張性」

このトレードオフをどう乗り越えるか、具体的なコード例と共に見ていきましょう。

 

🤔 共通の考え方:Destination = 型 + UIメタ情報

これまでの Navigation では String ベースの Route 管理が主流でしたが、これからの設計は

「型そのものに UI のメタ情報(ラベルやアイコンなど)を持たせる」

のが基本スタイルになります。

 

🤔 パターン 1:ネストする sealed interface

すべての Destination を一つの親インターフェースの中に閉じ込めるスタイルです。

実装イメージ

NavHost では AppDestination.xxx という形で指定します。

特徴

  • ◎ 視認性: 全ての画面遷移先が 1 ファイルにまとまっており、全体像を把握しやすい。
  • ◎ シンプル: 小〜中規模のアプリであれば、管理コストが最小限で済みます。
  • △ 拡張性: 全てが AppDestination に依存するため、機能(Feature)ごとにモジュールを分割しようとすると、循環参照が発生しやすくなります。

 

🤔 パターン 2:ネストしない(トップレベル) sealed interface

インターフェースを定義しつつ、各 Destination は独立したクラスとして定義するスタイルです。

実装イメージ

NavHost での記述はよりフラットになります。

特徴

  • ◎ 疎結合: 各 Destination を別ファイルや別モジュールに切り出しやすいため、Feature 単位の分割に強い。
  • ◎ 大規模向き: チーム開発でコンフリクトを避けやすく、ビルド速度向上のためのマルチモジュール化にも適しています。
  • △ 記述量: クラス名が重複しないよう xxxDestination と命名する必要があり、少し冗長に感じることがあります。

 

🤔 どちらを選ぶべきか?

設計の選択基準は非常にシンプルです。

 

🤔 まとめ

Navigation3 時代の Destination 設計の肝は
「型自体にメタ情報を持たせること」
です。

  • とりあえず作り始めるなら「ネスト型」
  • 将来的な機能拡張やモジュール化を見越すなら「非ネスト型」

アプリの規模と、将来どこまで成長させるかに合わせて選んでみてください。


Jetpack Compose Foundation サンプル目次リンク

Jetpack Composeの核心を担う androidx.compose.foundation

そのサンプルコード群は、Googleのエンジニアが「正しい書き方」を提示している宝庫です。

今回は、これらを実務での利用頻度とモダンな設計(2026年現在のトレンド)に基づいてグループ分けしました。

 

🧑🏻‍💻 1. インタラクション & ジェスチャー(操作感のキモ)

ユーザーが画面に触れた時の挙動を制御する、最も重要なグループです。

 

🧑🏻‍💻 2. スクロール & リスト(データの表示)

効率的にスクロールさせるためのテクニック集です。

 

🧑🏻‍💻 3. テキスト & 入力(文字の表示と編集)

2026年のトレンドである「次世代入力」が含まれます。

 

🧑🏻‍💻 4. 描画 & 視覚効果(見た目のクオリティ)

 

🧑🏻‍💻 5. 高度なシステム統合・同期

 

🧑🏻‍💻 これだけは読んでおくべきトップ5

1. ClickableSamples.kt(すべての基本)
2. LazyDslSamples.kt(リスト表示の要)
3. AnchoredDraggableSample.kt(モダンなUIに必須)
4. BasicTextFieldSamples.kt(入力の実装)
5. CanvasSamples.kt(カスタムUIの第一歩)

ぐらいか。


5-Minute TLS/SSL Troubleshooting Playbook - IP-direct access only (curl / openssl)

 

🧑🏻‍💻 Introduction

When you go through DNS, you can be misled by:

  • caching
  • load balancers / CDNs
  • name-resolution mistakes

This guide standardizes all commands to IP-direct access + correct SNI so you can isolate the real cause quickly.

 

🧑🏻‍💻 Prerequisite Variables


DOMAIN=example.com 
IP=1.2.3.4

 

🧑🏻‍💻 Overall Flow


① Check reachability with curl (IP direct) 
    ↓ 
② Read certificate verification result 
    ↓ 
③ Get raw TLS data with openssl 
    ↓ 
④ Check certificate expiration 
    ↓ 
⑤ Verify SAN 
    ↓ 
⑥ Check intermediate certificate 
    ↓ 
⑦ Verify TLS versions

 

🧑🏻‍💻 ① HTTP Reachability (IP direct + SNI)


curl -v https://$DOMAIN \
 --resolve $DOMAIN:443:$IP \
 -o /dev/null

OK


* Connected to example.com (1.2.3.4) port 443
* SSL certificate verify ok.
< HTTP/1.1 200 OK

Failure


Connection refused

  • nginx / apache not running
  • closed port
  • firewall

 

🧑🏻‍💻 ③ Raw TLS Layer Information


openssl s_client \
 -connect $IP:443 \
 -servername $DOMAIN

OK


CONNECTED(00000003)
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Verify return code: 0 (ok)

 

🧑🏻‍💻 ④ Certificate Expiration


openssl s_client \
 -connect $IP:443 \
 -servername $DOMAIN 2>/dev/null \
 | openssl x509 -noout -dates


notAfter=May 2 23:59:59 2026 GMT

 

🧑🏻‍💻 ⑤ SAN (Domain Match)


openssl s_client \
 -connect $IP:443 \
 -servername $DOMAIN \
 | openssl x509 -noout -ext subjectAltName


DNS:example.com
DNS:www.example.com

 

🧑🏻‍💻 ⑥ Missing Intermediate Certificate Check


openssl s_client \
 -connect $IP:443 \
 -servername $DOMAIN \
 -showcerts

OK


Certificate chain
 0 s:CN = example.com
 1 s:C = US, O = Let's Encrypt, CN = R3

Missing


Certificate chain
 0 s:CN = example.com

→ fullchain.pem not configured

 

🧑🏻‍💻 ⑦ TLS Version Restrictions

TLS 1.2


curl --tlsv1.2 -v https://$DOMAIN \
 --resolve $DOMAIN:443:$IP \
 -o /dev/null

TLS 1.3


curl --tlsv1.3 -v https://$DOMAIN \
 --resolve $DOMAIN:443:$IP \
 -o /dev/null


unsupported protocol

→ ssl_protocols misconfiguration

 

🧑🏻‍💻 ⑧ Detect SNI Misconfiguration (intentionally omit it)


openssl s_client -connect $IP:443


subject=CN = default.example.net

→ default certificate returned
→ virtual host configuration issue

 

🧑🏻‍💻 Copy-Paste 5-Minute Diagnosis Set



DOMAIN=example.com 
IP=1.2.3.4 

curl -v https://$IP \
 -H "Host: $DOMAIN"\
 -o /dev/null 

openssl s_client -connect $IP:443 \
 -servername $DOMAIN -brief 

openssl s_client -connect $IP:443 \
 -servername $DOMAIN 2>/dev/null \
 | openssl x509 -noout -dates 

openssl s_client -connect $IP:443 \
 -servername $DOMAIN \
 | openssl x509 -noout -ext subjectAltName

 

🧑🏻‍💻 Root-Cause Shortcut Map


Cannot connect even with IP direct
 → server or firewall 

Verify error 
 → intermediate certificate 

Expired
 → certificate renewal missed

SAN mismatch
 → wrong certificate selected 

Different cert without SNI
 → virtual host configuration 

Only one of TLS1.2 / 1.3 fails
 → protocol restriction

 

🧑🏻‍💻 Summary

By eliminating DNS and fixing:

  • IP-direct access
  • correct SNI

your TLS troubleshooting speed improves dramatically.

This workflow is ready to copy-paste in real incidents.

👉 openssl-s_client - OpenSSL Documentation
👉 curl - SSL CA Certificates