Navigation 3 時代でも ViewModel は State Holder であるべきか?

Compose と Navigation 3 が変えた ViewModel の役割

長年、Android 開発では一つの考え方が当たり前とされてきました。

画面の状態(State)は ViewModel に持たせる。

これは単なるベストプラクティスではなく、Android アーキテクチャの基本原則の一つでした。

ViewModel は Configuration Changes(画面回転など)でも破棄されず、さらに SavedStateHandle を使えば Process Death 後の状態復元もできます。

そのおかげで Activity や Fragment は UI を表示することに専念でき、画面の状態はすべて ViewModel が管理する、という設計が一般的になりました。

しかしここ数年で、この考え方は少しずつ変わり始めています。

Compose も Navigation 3 も ViewModel を不要にしたわけではありません。

代わりに、それぞれが 「本来その State を持つべき場所」 を用意するようになりました。

そこで、こんな疑問が生まれます。

Navigation 3 の時代でも、ViewModel は State Holder であるべきなのでしょうか?

 

🤔 以前の ViewModel は画面の State をすべて抱えていた

Compose が登場する以前は、多くの ViewModel が画面に関するほぼすべての状態を保持していました。


ViewModel
├── UI State
│   ├── text
│   ├── scrollPosition
│   ├── selectedTab
│   └── dialogState
│
├── Navigation State
│   └── screen arguments
│
└── Presentation Logic

この構成には多くのメリットがありました。

- Configuration Changes に強い
- Process Death 後も状態を復元できる
- Activity / Fragment をシンプルに保てる
- UI とビジネスロジックを分離できる

そのため、新しい State が増えたら、

とりあえず ViewModel に置く

という設計が自然になっていました。

 

🤔 Compose は UI State を UI に戻した

Jetpack Compose は、この考え方を少し変えました。

すべての UI State を ViewModel に持たせる必要はなく、Composable 自身が State を持てるようになったのです。

例えば検索文字列なら、


var query by remember {
    mutableStateOf("")
}

だけで十分です。

さらに Configuration Changes や Process Death を越えて保持したいなら、


var query by rememberSaveable {
    mutableStateOf("")
}

を使えます。

例えば次のような State は、

- TextField の入力内容
- 選択中のタブ
- 展開状態
- ダイアログの表示状態
- スクロール位置

どれも 純粋な UI State です。

ビジネスロジックではありません。

そのため、実際に使っている Composable の中に置く方が責務も分かりやすくなります。

Compose はまず、

「UI State は UI が持つ」

という考え方を広めました。

 

🤔 Navigation 3 はさらに一歩進めた

Navigation 3 は、この流れをさらに推し進めています。

まず、画面引数を SavedStateHandle 経由で受け渡す必要がなくなりました。

各画面は自分専用の NavKey を持ちます。


data class UserDetail(
    val userId: Long
) : NavKey

さらに Navigation 3 では rememberSerializable が追加されました。

rememberSaveable が Android の保存・復元機構を利用するのに対し、

rememberSerializableNavEntry 単位 で UI State を保持します。

つまり、UI State は Navigation の Back Stack と一緒に移動するようになります。

画面ごとの State を ViewModel に保存する必要はなく、

各画面自身が UI State を持つ

という構造になります。

役割を整理すると次のようになります。


UI State
    │
rememberSerializable
    │
Composable


Navigation State
    │
NavKey


Presentation Logic
    │
ViewModel

UI State は UI のもの。

Navigation State は Navigation のもの。

どちらも、もう ViewModel が管理すべき State ではありません。

 

🤔 では ViewModel には何が残るのか?

UI State も Navigation State も ViewModel の役割ではなくなったとしたら、

ViewModel は何を担当するのでしょうか?

実は、やることはまだたくさんあります。


ViewModel
├── Repository の調整
├── ビジネスロジック
├── Flow の変換
├── Paging
├── Retry
└── viewModelScope

ここで面白いことがあります。

この中には、

State そのものはほとんどありません。

あるのは責務です。

ViewModel は

- Repository からデータを取得し
- ユーザー操作に応答し
- ビジネスロジックを実行し
- UI が描画しやすい形へ加工する

という役割を担います。

 

🤔 ViewModel は「State Holder」ではなく「State Producer」へ

例えば次のような ViewModel を考えてみます。


val uiState = combine(
    userRepository.users,
    settingsRepository.settings
) { users, settings ->
    HomeUiState(
        users = users,
        darkMode = settings.darkMode
    )
}.stateIn(...)

ここで実際の State はどこにあるでしょうか。

ViewModel ではありません。

データの正しい管理場所(Single Source of Truth)は Repository にあります。

ViewModel は、それらを UI 用に変換しているだけです。

つまり ViewModel は

State を保持する存在ではなく、State を生成する存在

になりつつあります。

一見小さな違いに思えますが、

Presentation Layer の考え方を大きく変える変化です。

 

🤔 State を持つ責任は、それぞれのレイヤーへ

Compose より前は、


ViewModel
├── UI State
├── Navigation State
└── Presentation Logic

という構成が一般的でした。

Navigation 3 では、


rememberSerializable
└── UI State

NavKey
└── Navigation State

ViewModel
└── Presentation Logic

へと役割が整理されます。

Compose は UI State を UI に戻しました。

Navigation 3 は Navigation State を NavKey に任せ、

UI State を NavEntry ごとに保持できるようにしました。

ViewModel が不要になったわけではありません。

責務がより明確になった のです。

 

🤔 よりシンプルなアーキテクチャ

その結果、アーキテクチャは次のように整理できます。


+----------------------+
|      Composable      |
|----------------------|
| UI State             |
| rememberSerializable |
+----------+-----------+
           |
           v
+----------------------+
|      ViewModel       |
|----------------------|
| Presentation Logic   |
| Flow Transformation  |
| Repository Calls     |
+----------+-----------+
           |
           v
+----------------------+
|      Repository      |
|----------------------|
| Persistent Data      |
| DataStore / Database |
+----------------------+

各レイヤーが、それぞれ最も適した責務を持ちます。

- Composable は UI State
- NavKey は Navigation State
- Repository は永続データ
- ViewModel は Presentation Logic

責務が自然に分離された構造です。

 

🧑🏻‍💻 まとめ

Navigation 3 が登場しても、ViewModel が不要になるわけではありません。

ViewModel はこれまでどおり、

- ビジネスロジック
- Repository の調整
- Flow の変換
- Paging
- 長時間動作する Coroutine

などを担当する最適な場所です。

一方で、Compose は rememberSaveable を導入し、UI State を UI に持たせやすくしました。

さらに Navigation 3 は rememberSerializable を追加し、UI State を NavEntry ごとに保持できるようにしました。また、Navigation State は NavKey が担当します。

その結果、「とりあえず ViewModel に State を置く」という設計は、必ずしも最適ではなくなりました。

これからは、

この State を ViewModel に保存するにはどうすればいいか?

ではなく、

そもそも、この State は ViewModel が持つべきものなのか?

と考える方が自然でしょう。

ViewModel は今でも欠かせない存在です。

しかし Navigation 3 の時代における主な役割は、State Holder ではなく Presentation Logic を担うことへと変化しつつあります。

なお、UI State を ViewModel の外へ移したからといって、Configuration Changes や Process Death に弱くなるわけではありません。

- UI State は rememberSaveablerememberSerializable
- Navigation State は NavKey
- 永続データは Repository

というように、それぞれのレイヤーが適切な仕組みを使えば、Configuration Changes と Process Death の両方に対応した堅牢なアプリケーションを構築できます。

重要なのは State を保存することではなく、

「その State を最も自然に所有すべきレイヤーが管理すること」なのです。

 

🧑🏻‍💻 参考


Navigation 3 の Navigation Graph パターンまとめ

 

🤔 Navigation Graph はもう navigation() だけじゃない

Jetpack Compose 向けの Navigation 3 では、従来の Navigation Component にあった navigation() を使ったネストした Navigation Graph の考え方がなくなりました。

その代わりに、「コードをどう整理するか」と「BackStack をどう分けるか」を分離して考える設計になっています。

この記事では、Navigation 3 でよく使われる 3 つの Navigation Graph パターンを紹介します。

 

🤔 Navigation 3 の3つのパターン


Navigation 3
│
├─ コードを整理したい
│   ├─ Extension Functions
│   └─ Sealed NavKeys
│
└─ BackStack を分けたい
    └─ Nested NavDisplay

ポイントは、

- コードを整理する方法
- BackStack を分離する方法

は別の話だということです。

 

🤔 Extension Functions で画面を機能ごとに分割する

Navigation 3 では entry() を並べて画面を登録します。

画面数が増えてくると、1つのファイルに全部書くのは見づらくなります。

そんなときは Extension Function へ切り出すのが最もシンプルです。


// AuthModule.kt
fun EntryProviderScope<NavKey>.authGraph() {
    entry<Login> {
        LoginScreen()
    }

    entry<SignUp> {
        SignUpScreen()
    }
}

メイン側では呼び出すだけです。


val entryProvider = entryProvider {
    authGraph()

    entry<Home> {
        HomeScreen()
    }
}

NavDisplay(
    backStack = backStack,
    entryProvider = entryProvider
)

これだけで認証画面を別ファイルへ分離できます。

メリット
- Feature 単位で管理できる
- モジュール分割しやすい
- BackStack は1つなので構成はシンプル

 

🤔 Sealed NavKey で画面をグループ化する

もう1つの整理方法が NavKey をグループ化する 方法です。


sealed interface AuthKey : NavKey {

    @Serializable
    data object Login : AuthKey

    @Serializable
    data object SignUp : AuthKey
}

sealed interface MainKey : NavKey {

    @Serializable
    data object Home : MainKey

    @Serializable
    data class Detail(
        val id: String
    ) : MainKey
}

画面遷移も名前空間付きで分かりやすくなります。

Navigation 3 では Graph は存在しませんが、

- Auth 系
- Main 系
- Settings 系

のように論理的に整理できます。

メリット
- 名前空間が分かりやすい
- 型安全
- Destination が探しやすい

 

🤔 NavDisplay をネストする

ここだけは少し意味が変わります。

これはコード整理ではなく BackStack を分離する方法です。

例えば

- Bottom Navigation
- タブ
- Onboarding
- 独立したフロー

では、それぞれ独自の履歴を持たせたいことがあります。

その場合は NavDisplay をネストします。


entry<MainTabs> {
    MainTabsScreen()
}


@Composable
fun MainTabsScreen() {

    val tabBackStack =
        rememberNavBackStack(HomeTab)

    NavDisplay(
        backStack = tabBackStack,
        entryProvider = entryProvider {

            entry<HomeTab> {
                HomeScreen()
            }

            entry<SearchTab> {
                SearchScreen()
            }

            entry<ProfileTab> {
                ProfileScreen()
            }
        }
    )
}

ここでは親とは別の NavBackStack を持っています。


Root 
NavDisplay
    │
    └── MainTabs
             │
             └── NavDisplay
                     │
                     ├── Home
                     ├── Search
                     └── Profile

つまり、

NavDisplay = NavBackStack の管理単位

という考え方になります。

Bottom Navigation で各タブの履歴を保持できるのも、この仕組みによるものです。

 

🤔 どれを選べばいい?

次のように考えると分かりやすいでしょう。


画面を整理したい?
│
├─ Yes
│   ├─ Extension Functions
│   └─ Sealed NavKeys
│
└─ BackStack を分けたい?
    └─ Nested NavDisplay

 

🤔 まとめ

Navigation 3 では、従来の navigation() による Graph のネストはありません。

その代わりに、

- Extension Functions で entry() を機能ごとに分割する
- Sealed NavKey で Destination を整理する
- Nested NavDisplay で独立した NavBackStack を持つ

という3つのパターンを組み合わせて設計します。

特に重要なのは、コードの整理と BackStack の分離は別の概念という点です。

Navigation 3 は Graph をネストするライブラリではなく、NavDisplay と NavBackStack を組み合わせてナビゲーション構造を組み立てるライブラリへと進化しています。

 

🤔 参考


Navigation 3 時代の「二重遷移」を防ぐ正しい方法 - dropUnlessResumed は debounce ではない

Android アプリでボタンを素早く連打すると、同じ画面が何度も積み重なってしまうことがあります。


Home
  ↓
Detail
  ↓
Detail
  ↓
Detail

この問題を防ぐために debouncethrottle を使うケースは少なくありません。

しかし、Navigation 3 が提供する dropUnlessResumed は考え方がまったく異なります。

これは「一定時間タップを無視する」のではなく、画面が遷移できる状態かどうかを見てイベントを受け付ける API です。

 

🤔 debounce との違い

一見すると似ていますが、見ているものが違います。

つまり、


debounce
    ↓
「まだ500ms経ってないから無視」

dropUnlessResumed
    ↓
「この画面はもう操作できないから無視」

という違いがあります。

 

🤔 Navigationでは「時間」より「状態」が重要

画面遷移が始まると、現在の画面はすぐに RESUMED ではなくなります。


RESUMED
    │
ボタン押下
    │
navigate()
    │
STARTED
    │
STOPPED

この間にもう一度クリックされても、


dropUnlessResumed {
    navController.navigate(...)
}

であれば実行されません。

つまり、


クリック1回目
    ↓
navigate()

クリック2回目
    ↓
画面はもう RESUMED じゃない
    ↓
無視

となります。

 

🤔 なぜ debounce より自然なのか

例えば画面遷移アニメーションが長くなったとします。

debounce は

500ms待つ

という固定時間なので、

- アニメーションが300ms
- アニメーションが700ms

どちらにも最適とは限りません。

一方 dropUnlessResumed


画面が操作可能
      ↓
受け付ける

画面遷移中
      ↓
受け付けない

戻ってきた
      ↓
再び受け付ける

と、Lifecycle に合わせて自動的に動作します。

 

🤔 内部では何をしているの?

実装は驚くほどシンプルです。

概念的には次のような処理です。


if (lifecycle.currentState == Lifecycle.State.RESUMED) {
    block()
}

つまり、

- 現在の Lifecycle を確認する
- RESUMED のときだけラムダを実行する

それだけです。

タイマーも、Coroutine も、待ち時間もありません。

 

🤔 Navigation 3 での使い方

Compose ではクリックイベントをそのまま包むだけです。


val onClick = dropUnlessResumed {
    navController.navigate(Detail)
}

Button(
    onClick = onClick
) {
    Text("Open")
}

これだけで、

- 二重 Push
- 二重画面生成
- 連打による BackStack の重複

を簡単に防げます。

 

🤔 debounce を使うべき場面

もちろん debounce が不要になったわけではありません。

例えば

- 検索ボックス
- API リクエスト
- テキスト入力
- リアルタイム検索

のように「連続イベントを間引く」目的なら debounce が適しています。

一方、

- Navigation
- Dialog を開く
- BottomSheet を表示する

など Lifecycle に依存する UI 操作では dropUnlessResumed の方が自然です。

 

🤔 まとめ

dropUnlessResumeddebounce の代替ではありません。

見る対象が時間ではなく Lifecycle だからです。

Navigation では「今この画面は操作可能か」が最も重要になります。

そのため Navigation 3 では、時間ベースの制御ではなく Lifecycle ベースの制御で二重遷移を防ぐ設計になっています。

一度仕組みを理解すると、「なぜ Navigation 用に専用 API が用意されているのか」がよく分かるはずです。

 

🤔 参考


Composeの初期化処理を snapshotFlow と first で待つ

Composeで画面を初期化するとき、次のような問題に遭遇することがあります。


画面が表示される
 ↓ 
Stateを復元する
 ↓ 
非同期データを読み込む
 ↓
UIにデータが反映される
 ↓ 
初期処理を実行する

例えば、保存していたスクロール位置を復元したいとします。

しかし、LaunchedEffect が実行された時点では、まだ Grid にアイテムが存在しないかもしれません。


LaunchedEffect(Unit) { 
    gridState.scrollToItem( 
        savedPosition.index, 
        savedPosition.offset 
    ) 
}

この場合、保存された位置が100番目だったとしても、初期状態のGridにはまだ20個しかアイテムがない可能性があります。


LaunchedEffect
 ↓ 
totalItemsCount = 20
 ↓ 
scrollToItem(100)
 ↓ 
⚠️ まだ100番目のアイテムが存在しない

そこで、snapshotFlowfirst を使います。

 

🧑🏻‍💻 snapshotFlow { }.first { }というパターン


snapshotFlow { 
    composeState 
}.first { 
    condition 
}

これは、次のような処理です。


composeState を監視
 ↓ 
State が変化する
 ↓ 
条件を確認
 ↓ 
condition を満たす
 ↓
次の処理へ進む

first は、条件を満たす最初の値を受け取るとFlowの収集を終了します。

そのため、

UIの状態が初期処理に必要な状態になるまで待つ

のような用途に向いています。

 

🧑🏻‍💻 スクロール位置を復元する

次のように書けます。


LaunchedEffect(Unit) { 
    val savedPosition = viewModel.savedScrollPosition 

    snapshotFlow {
        gridState.layoutInfo.totalItemsCount 
    }.first { 
        it > savedPosition.index 
    } 

    gridState.scrollToItem( 
        savedPosition.index, 
        savedPosition.offset 
    ) 
}

ここで重要なのは、次の2つの状態は同じではないことです。


データが存在する
  ≠
UIのLayoutにアイテムが反映されている

ViewModel がデータを保持していても、Compose の Layout がまだ計算されていないことがあります。

このコードでは、


gridState.layoutInfo.totalItemsCount

を監視しています。

そして、


.first { 
    it > savedPosition.index 
}

によって、保存された位置までアイテムがGridに反映されるのを待ちます。

例えば、保存位置が100の場合は次のようになります。


totalItemsCount = 0
 ↓ 
totalItemsCount = 50
 ↓ 
totalItemsCount = 100
 ↓ 
totalItemsCount = 120
 ↓ 
条件成立
 ↓ 
scrollToItem(100)

このように、scrollToItem() を実行するタイミングを、単なる画面表示時ではなく、UIが実際に必要な状態になったタイミングに合わせることができます。

 

🧑🏻‍💻 なぜ collect ではないのか

snapshotFlow は Flow なので、通常は次のように collect できます。


snapshotFlow {
    gridState.layoutInfo.totalItemsCount
}.collect { count ->
    // countが変化するたびに実行
}

しかし、初期化処理では、継続的に監視したいわけではありません。


必要な状態になるまで待つ
 ↓
初期処理を実行する
 ↓ 
監視を終了する

この場合は、


.first { condition }

が適しています。


snapshotFlow { 
    gridState.layoutInfo.totalItemsCount 
}.first { 
    it > savedPosition.index 
} 

gridState.scrollToItem(...)

first が条件を満たすと、次の処理に進みます。


snapshotFlow
  ↓
first
  ↓
条件成立
  ↓
Flow終了
  ↓
scrollToItem()

 

🧑🏻‍💻 collect と first の使い分け

例えば、スクロール位置を継続的に監視する場合は、collect が自然です。


LaunchedEffect(listState) { 
    snapshotFlow { 
        listState.firstVisibleItemIndex 
    }.collect { index -> 
        // スクロールするたびに処理 
    } 
}

一方、初期状態が揃うまで待つ場合は、first が自然です。


LaunchedEffect(Unit) {
    snapshotFlow {
        gridState.layoutInfo.totalItemsCount
    }.first {
        it > savedPosition.index
    }

    // 初期処理
}

 

🧑🏻‍💻 まとめ

snapshotFlow { }.first { } は、次のようなコードとして理解すると分かりやすいです。


snapshotFlow {
    UIの状態
}.first {
    初期処理に必要な状態になった
}

初期処理()

つまり、

Compose UIの状態が揃うまで待ってから、初期処理を実行する

ためのパターンです。

特に、非同期データの読み込みと Compose の Layout 計算のタイミングがずれるケースでは、非常に有効です。

LazyGridState.layoutInfo のようなLayout情報は、再レイアウトやスクロールによって更新される Observable な状態であり、Android公式ドキュメントでも副作用で扱う場合は snapshotFlow を使う方法が示されています。


Kotlin by JetBrains「Jake Wharton | KotlinConfersations'26」の文字起こし

Android開発やKotlinコミュニティのキーパーソンであるジェイク・ウォートン(Jake Wharton)氏へのインタビューです。

 

🤔 1. 自己紹介とKotlinとの歩み [00:26]

経歴:
長年Androidデベロッパーとして活動。Square(現Block)、Cash Appを経て、現在はSkylightに在籍。

Kotlinとの出会い:
Square時代、Java 7の機能不足やGoogleのツール進化の遅さに悩んでいた「プレ1.0(正式リリース前)」の頃にKotlinに注目。社内向けに導入提案書(プロポーザル)を作成。

反響:
その提案書を公開したところコミュニティで大きな話題となり、SquareでのKotlin導入だけでなく、エコシステム全体の盛り上がりに繋がった。

 

🤔 2. AndroidにおけるKotlinとコミュニティの力 [01:50]

Googleでの活動:
一時期Googleに籍を置き、AndroidにおけるKotlinの公式サポート(KTXライブラリなどの立ち上げ)を支援。

エコシステムの進化:
KTXライブラリのコード自体は現在通常のライブラリに統合されて役目を終えたが、それは言語が成熟した良い証拠であると語る。

Apple(Swift)とのアプローチの違い:
iOSのSwiftがAppleという中央集権からトップダウンで提供されたのに対し、AndroidにおけるKotlinは「草の根(グラスルーツ)的なコミュニティの熱意」から始まり、最終的にGoogleが公式サポートせざるを得ない流れを作った点がユニークである。

非Androidへの広がり:
「KotlinはGoogle製でも、Android専用でもない」という点が、15年経った今ではAndroid以外の開発者にも広く認知されるようになった。

 

🤔 3. Kotlin Multiplatform (KMP) の挑戦 [05:35]

これまでのクロスプラットフォーム:
過去のXamarinやPhoneGap、現代のReact NativeやFlutterなどを評価した上で、Cash App時代にKotlin Multiplatform(KMP)とCompose Multiplatformを選択。

アプローチの特徴:
UI層はiOSならUIKit/SwiftUI、WebならHTML DOM、AndroidならCompose UIといった「各プラットフォーム独自のネイティブビュー」を尊重し、バックエンドのビジネスロジックやプレゼンターロジックのみを共通化(シェア)する方針をとった。

KMPの強み:
他のクロスプラットフォーム言語と異なり、WebならJavaScript、iOSならネイティブコード、AndroidならJavaバイトコードと、ターゲットごとに最適な形へコンパイルされるため、メモリ空間の競合や相互運用の壁(Interop layer)が少ない。

エコシステムへの貢献:
5年前に始めた当時はJetBrainsのComposeも初期段階だったため、自分たちで足りないターゲットを作るなどしてエコシステムを強力にプッシュした。

 

🤔 4. オープンソース(OSS)の重要性 [10:53]

キャリアへの影響:
オープンソースに貢献することで、自身のスキルアップだけでなく、新しい人との出会いや転職の機会など、キャリアの節目で何度も救われた。

企業とOSSの関係:
REST APIとの通信や画像読み込み、依存関係の解決(DI)など、どのアプリでも共通する「ビジネスの知的財産(IP)ではない部分」のコードはオープンにすべき。結果的に会社の採用活動(「OSSを見て応募した」という優秀な人材の獲得)など、数字に表れにくい大きなリターン(無形の資産)をもたらす。

 

🤔 5. 生成AI(LLM)とライブラリの未来 [15:11]

「AIがコード生成できるならライブラリ投資は不要か?」という問いへの持論:
「コードを書くこと(Writing code)」自体は、ソフトウェア開発において決して最も難しい部分ではない。AIは既存のスキルを加速させる(アクセラレーター)ツールとして使うべきであり、それなしではコードが書けないような依存の仕方はリスクがある(将来的なコスト高騰も含めて)。

コードは書かれる回数より、読まれる回数の方が10倍多い(Code is read 10 times more than it's written)。 要件は常に変わるため、全体を俯瞰して理解できるコード設計が重要。

ライブラリの役割:
ライブラリの真の価値は「再利用可能なコードの境界線(デリミテーション)」を明確に引き、人間の認知負荷を下げることにある。パレートの法則のように「80〜85%の共通ユースケース」を綺麗にカプセル化することが大切。人間にとって直感的で優れた設計は、言語モデル(AI)にとっても扱いやすいはずである。

 

🤔 6. 2026年現在のKotlinへの期待 [22:45]

K2コンパイラの恩恵:
長年開発が続けられてきた「K2コンパイラ」への移行(大きな山場)を乗り越えたことで、言語自体の進化スピードが再び加速している。

注目している新機能:
Rich Errors(エラー処理の改善)プロポーザルの刷新
未使用の戻り値チェッカー(Unused return value checker)
when 式のデフォルト網羅性(Exhaustive when by default)

現在の心境:
K2の開発に全力を注いでいた停滞期を抜け出し、標準ライブラリ(datetimeやco-routinesなど)や言語プロポーザルが活発に進化している今の状況は、初期のKotlinのワクワク感を思い出させる。

 

🤔 7. コミュニティへの参加に気後れしている人へのアドバイス [27:30]

提案書(KEEP)の難しさ:
KEEP(Kotlin Evolution and Enhancement Process)はコンパイラの構文解析や各プラットフォームのバイトコードまで考慮しなければならず、内容が非常に高度で圧倒されるのは当然。

おすすめの関わり方:
Kotlin公式Slack(Kotlinlang Slack)にある language-proposal や language-evolution チャンネルを覗いてみるのがおすすめ。そこでは、より人間的で身近な困りごとや質問が、消化しやすい形で活発に議論されている。