SimpleLoader Generation Skill
非同期データ読み込みの状態管理を sealed interface ベースのステートマシンとして生成する。 初回読み込み (initialLoad) とリフレッシュ (refresh) を明確に区別し、Compose UI ヘルパーと組み合わせて使う。
Usage
確認事項
コード生成前に以下を確認する。ユーザーの指示から明確に読み取れる項目は確認を省略してよい。
- 対象モジュール — SimpleLoader を配置するモジュール (例:
ui/core,shared) - パッケージ名 — 既存の構成から推定し提案
- 生成ファイル (parts) — 以下から選択 (デフォルト: 全て)
-
core—SimpleLoader.kt+SimpleLoaderFactory.kt(必須) -
logger—SimpleLoaderLogger.kt(core が参照するため必須) -
handler—IllegalStateTransitionHandler.kt(core が参照するため必須) -
ext—SimpleLoaderExt.kt(dataOrNull,exceptionOrNull) -
partial—SimpleLoaderWithPartialData.kt(部分データ対応) -
ui— UI ヘルパー (View.kt,AnimatedView.kt,InitialLoadingView.kt,InitialErrorView.kt)
-
スキップ条件
以下の場合は確認をスキップしてデフォルト設定で生成:
- "デフォルトで" / "全部生成して" / "with default settings"
生成手順
Step 1: プロジェクト解析
build.gradle.ktsを確認し KMP / Android / JVM を判定- ソースディレクトリのパターンを特定:
- KMP:
<module>/src/commonMain/kotlin/ - Android/JVM:
<module>/src/main/kotlin/
- KMP:
- 既存の Loader 系クラスがないか検索
Step 2: install script の実行
${CLAUDE_SKILL_DIR}/scripts/install.sh を実行する。
script を読解・書き換え・再実装せず、そのまま実行する。
"${CLAUDE_SKILL_DIR}/scripts/install.sh" \
--package <USER_PACKAGE> \
--dest <TARGET_DIR> \
--parts core,logger,handler,ext,partial,ui # 確認事項 3 の選択。省略時は全て
--destは--packageに対応するソースディレクトリ (例:shared/src/commonMain/kotlin/com/myapp/loader)core/logger/handlerは相互参照のため常に生成される (省略しても自動追加)- 既存ファイルがあるとエラーで停止する。ユーザーが上書きを明示した場合のみ
--forceを付けて再実行 --dry-runで書き込みなしに生成予定を確認できる- 成功時は stdout 最終行に 1 行 JSON (
{"ok":true,...}) が出力される。失敗時は stderr のFIX:に従う
生成されるファイル構成:
<TARGET_DIR>/
├── IllegalStateTransitionHandler.kt
└── simple/
├── SimpleLoader.kt # interface + State sealed interface + Impl + Fake
├── SimpleLoaderFactory.kt # Factory interface
├── SimpleLoaderExt.kt # dataOrNull, dataOr, exceptionOrNull
├── SimpleLoaderLogger.kt # fun interface Logger
├── SimpleLoaderWithPartialData.kt # PartialData support
└── ui/
├── View.kt # State<Data>.View() composable
├── AnimatedView.kt # State<Data>.AnimatedView() with transitions
├── InitialLoadingView.kt # Default loading indicator
└── InitialErrorView.kt # Default error with retry button
Step 3: 依存関係の確認
build.gradle.kts に以下が含まれているか確認し、不足があれば追加を提案:
kotlinx-coroutines-core— 必須kotlinx-serialization— State の@Serializableに必要 (オプション)androidx.compose— UI ヘルパーに必要androidx.lifecycle— ViewModel 連携に必要
Step 4: ビルド確認
# KMP
./gradlew :<module>:compileKotlinJvm
# Android
./gradlew :<module>:compileDebugKotlin
Step 5: 完了メッセージ
## 生成完了
**パッケージ**: `<package>`
**出力先**: `<output_dir>`
### 生成ファイル
- SimpleLoader.kt + SimpleLoaderFactory.kt
- ...
### 依存関係
- [変更なし / 追加: ...]
### ビルド結果
- [SUCCESS / FAILED]
利用パターン
SimpleLoader の実際の利用方法を以下に示す。references/ 内に詳細なサンプルコードがある。
パターン 1: typealias + Factory で Loader を定義
// 1. ドメイン固有の Loader 型を typealias で定義
typealias ItemListLoader = SimpleLoader<List<Item>>
// 2. Factory で実装を生成し、DI でバインド
class ItemListLoaderImpl(
getItemListUseCase: GetItemListUseCase,
simpleLoaderFactory: SimpleLoaderFactory,
) : ItemListLoader by simpleLoaderFactory.create(
coroutineScope = MainScope(),
load = { getItemListUseCase() },
)
パターン 2: ViewModel での利用
class HomeViewModel(
private val itemListLoader: ItemListLoader,
) : ViewModel(itemListLoader) { // AutoCloseable として渡す
// StateFlow をデリゲートで公開
val loadItemListState by itemListLoader::state
init {
itemListLoader.initialLoad() // 初回読み込み
}
fun refresh() = itemListLoader.refresh() // リフレッシュ
}
パターン 3: Compose UI での状態表示
@Composable
fun HomeScreen(viewModel: HomeViewModel) {
val state by viewModel.loadItemListState.collectAsStateWithLifecycle()
state.AnimatedView(
initialLoading = { SkeletonView() }, // カスタムローディング
) { state ->
// ViewWithDataScope 内: isRefreshLoading, isRefreshError が使える
ItemListView(
itemList = state.data,
isRefreshLoading = isRefreshLoading,
)
}
}
パターン 4: テストでの利用
// FakeSimpleLoader でテスト
val fakeLoader = FakeSimpleLoader<List<String>>(
coroutineScope = TestScope(),
state = SimpleLoader.State.Loaded(listOf("item1", "item2")),
)
val viewModel = HomeViewModel(itemListLoader = fakeLoader)
状態遷移図
Initial ──initialLoad()──► InitialLoading ──success──► Loaded
│ │
└──failure──► InitialError │
│
Loaded ──refresh()──► RefreshLoading ──success──► Loaded
│
└──failure──► RefreshError
load()は現在の状態に応じてinitialLoad()かrefresh()を自動選択する- Loading 中の
load()呼び出しはIllegalStateTransitionReasonで通知