パス設定の読み込み状態を StateFlow で観測可能に

hotwired/hotwire-native-android

PathConfigurationStateFlow ベースの読み込み状態管理が導入され、アプリはパス設定がいつ・どのソースから読み込まれたかを観測できるようになりました。新たに PathConfigurationLoadStatePathConfigurationData が追加され、状態の型安全な表現と軽量なデータ転送を実現しています。

背景

これまでの PathConfiguration は読み込み完了を通知する手段を持たず、設定がいつ利用可能になるかをアプリ側から知る方法がありませんでした。バンドルアセット・キャッシュ済みリモート・フレッシュリモートという3つのソースを持ちながら、どのソースから読み込まれたかの区別もできませんでした。

この制約は、設定の読み込み完了後に初期化処理を走らせたいケースや、キャッシュとリモートで異なる挙動を実装したいケースで問題となっていました。本PRはこれらのユースケースに対し、Kotlin の StateFlow を中心とした観測可能な状態管理の仕組みを導入することで対応しています。

技術的な変更

PathConfigurationLoadState — 型安全な状態の表現

PathConfigurationLoadState として、読み込み状態を型安全に表現する sealed interface が新設されました。状態は NotLoaded(初期状態)と Loaded(読み込み完了)の2系統に分かれ、Loaded はさらに3つのサブクラスに分岐します。

sealed interface PathConfigurationLoadState {
    data object NotLoaded : PathConfigurationLoadState

    sealed interface Loaded : PathConfigurationLoadState {
        val configuration: PathConfigurationData

        data class BundledAssetLoaded(override val configuration: PathConfigurationData) : Loaded
        data class CachedRemoteLoaded(override val configuration: PathConfigurationData) : Loaded
        data class RemoteLoaded(override val configuration: PathConfigurationData) : Loaded
    }
}

is Loaded で読み込み完了を一括チェックし、ソースを区別する必要がある場合のみ具体的なサブクラスに対して when 分岐するという2段階の判定が可能になっています。

PathConfigurationData — シリアライズ可能な軽量データクラス

PathConfigurationData は、PathConfiguration から設定データ(rulessettings)を分離した data class として新設されました。もともと PathConfiguration 自体に @SerializedName アノテーションが付いていた rulessettings フィールドを抽出し、Gson によるデシリアライズ対象をこのクラスに集約しています。

data class として定義されていることで equals()/hashCode() が自動生成され、StateFlow が同一内容の連続エミットを重複排除できます。PathConfigurationDataproperties(location: String) メソッドも備えており、URLに対するパス設定の検索ロジックが PathConfiguration から移されています。

PathConfiguration — StateFlow の公開と同期・非同期の分離

PathConfigurationloadState: StateFlow<PathConfigurationLoadState> が追加され、内部では MutableStateFlow として管理されます。

class PathConfiguration internal constructor() {
    private val _loadState = MutableStateFlow<PathConfigurationLoadState>(NotLoaded)
    private val loadingScope: CoroutineScope = CoroutineScope(dispatcherProvider.io + SupervisorJob())
    private var loadingJob: Job? = null

    val loadState: StateFlow<PathConfigurationLoadState> = _loadState.asStateFlow()
}

バンドルアセットとキャッシュ済みリモートの読み込みは load() 内で同期的に実行され、load() が返った時点で設定が即座に利用可能になります。リモートフェッチは Dispatchers.IO 上で非同期に実行されます。load() が複数回呼ばれた場合は前回の loadingJob がキャンセルされ、多重フェッチを防ぎます。状態の変更は synchronized ブロックで保護され、IOスレッドとメインスレッド間のスレッド安全性が確保されています。

PathConfigurationLoader — 読み込み順序の変更と責務の整理

PathConfigurationLoader はコルーチンスコープを自身で持つ構造から、ロード結果を戻り値として返す純粋な関数群に変わりました。コンストラクタから Context が除かれ、ロード関数の引数として受け取る設計に変更されています。

-internal class PathConfigurationLoader(val context: Context) : CoroutineScope {
+internal class PathConfigurationLoader {

読み込み順序にも変更があります。従来はバンドルアセットを先に読み込んでいましたが、今回からはキャッシュ済みリモート設定を優先します。キャッシュが存在しない場合、またはキャッシュのパースに失敗した場合にのみバンドルアセットにフォールバックします。これにより、以前リモートから取得した設定がある場合は初回起動時からそれが優先されます。

PathConfigurationRepository — OkHttp 5 の非同期 API に移行

依存ライブラリが OkHttp 4.12.0 から 5.3.2 に更新され、新たに okhttp-coroutines アーティファクトが追加されました。issueRequestsuspend 関数になり、call.execute() から call.executeAsync() へ変更されています。

-    private fun issueRequest(request: Request): String? {
-        return try {
-            val call = HotwireHttpClient.instance.newCall(request)
-            call.execute().use { response ->
+    private suspend fun issueRequest(request: Request): String? = try {
+        val call = HotwireHttpClient.instance.newCall(request)
+        call.executeAsync().use { response ->
+            withContext(dispatcherProvider.io) {

また response.body?.string() から response.body.string() へのnon-null化も行われており、OkHttp 5 での API 変更に追従しています。

DemoApplication — 初期化順序の修正

DemoApplication では Hotwire.loadPathConfiguration() の呼び出しが configureApp() の先頭から末尾に移動されました。これはパス設定の読み込み前に他の設定(JSONコンバーターなど)が確定している必要があるケースへの対応と見られます。

設計判断

StateFlow を採用したリアクティブな状態公開が本PRの中心的な設計判断です。コールバックによる完了通知ではなく StateFlow を選ぶことで、collectfirstfilter といった Flow 演算子をそのまま活用でき、「最初の読み込み完了を一度だけ待つ」「特定ソースの読み込みだけを監視する」といった多様なユースケースに対応できます。

後方互換性への配慮も随所に見られます。バンドルアセットの読み込みを同期的に維持することで、load() 後に即座に設定を参照する既存コードが壊れません。PathConfigurationDatadata class とし equals()/hashCode() を自動生成させることで、同一内容での再読み込みが StateFlow の不要な再エミットを引き起こさない設計になっています。

sealed interface による状態の階層化も注目すべき点です。Loaded を中間の sealed interface として設け、その下に具体的なソースを表すサブクラスを置く2層構造により、「読み込み完了かどうか」と「どこから読み込まれたか」を独立して扱えます。

まとめ

本PRは、パス設定の読み込みをブラックボックスから観測可能なプロセスへと変換した変更です。PathConfigurationLoadState の sealed 階層と StateFlow の組み合わせにより、バンドル・キャッシュ・リモートという3段階の読み込みシーケンスをアプリ側が細かく制御できるようになり、設定の可用性に依存した初期化ロジックの実装が型安全かつシンプルに記述できます。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
6db08e18

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文、背景、技術的な変更、設計判断、まとめが「総論→各論→結論」の構成に沿って明確に記述されています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライトやPR番号のリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

StateFlowやsealed interfaceなどの専門用語が適切に使用されており、対象読者であるエンジニアに適した技術レベルです。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクション、各パラグラフが論理的に構成されています。トピックセンテンスが明確で、1段落1トピックの原則が守られており、可読性が高いです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているコードブロックやdiffは、提供されたDiff情報と正確に一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

StateFlow, sealed interface, OkHttpなど、関連する技術用語が文脈に沿って正確に使用されています。PR Descriptionの僅かな不正確さ(Idle/NotLoaded, sealed class/interface)をコードベースで正しく記述している点も評価できます。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

StateFlow導入のメリット、データクラス化による重複排除、非同期処理の分離など、技術的な変更点に関する説明がPR情報とDiffに基づいており、正確かつ論理的です。

事実の突合 ⚠ WARNING

PR情報による主張の裏付け(ハルシネーション検出)

ほぼすべての主張がPR情報で裏付けられていますが、「DemoApplication」の変更理由について、PR情報にない推測が含まれています。ただし、コードの文脈から妥当な推測です。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#195)やOkHttpのバージョン番号(4.12.0, 5.3.2)など、記事中の数値や固有名詞はすべて正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトル「パス設定の読み込み状態を StateFlow で観測可能に」は、PRの主題「Observe the path configuration loading state」を的確に反映しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事の内容はPR情報とDiffに基づいており、バージョンサポート状況など、PRに記載のない外部知識の不適切な追加はありません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「導入され」などの時間表現はPRの文脈と一致しており、時間的な歪曲はありません。