Files
everything-claude-code/docs/ja-JP/skills/compose-multiplatform-patterns/SKILL.md
Claude ec9ace9c54 docs: add native Japanese translation of ECC documentation (ja-JP)
Translate everything-claude-code repository to Japanese including:
- 17 root documentation files
- 60 agent documentation files
- 80 command documentation files
- 99 rule files across 18 language directories (common, angular, arkts, cpp, csharp, dart, fsharp, golang, java, kotlin, perl, php, python, ruby, rust, swift, typescript, web)
- 199 skill documentation files

Total: 455 files translated to Japanese with:
- Consistent terminology glossary applied throughout
- YAML field names preserved in English (name, description, etc.)
- Code blocks and examples untouched (comments translated)
- Markdown structure and relative links preserved
- Professional translation maintaining technical accuracy

This translation expands ECC accessibility to Japanese-speaking developers and teams.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-17 02:31:40 -04:00

9.1 KiB
Raw Blame History

name, description, origin
name description origin
compose-multiplatform-patterns KMPプロジェクト向けのCompose MultiplatformおよびJetpack Composeパターン — 状態管理、ナビゲーション、テーマ設定、パフォーマンス、プラットフォーム固有のUI。 ECC

Compose Multiplatformパターン

Compose MultiplatformとJetpack Composeを使用して、Android、iOS、デスクトップ、Web間で共有UIを構築するためのパターン。状態管理、ナビゲーション、テーマ設定、パフォーマンスをカバーします。

起動条件

  • Compose UIの構築Jetpack ComposeまたはCompose Multiplatform
  • ViewModelとCompose状態によるUI状態の管理
  • KMPまたはAndroidプロジェクトでのナビゲーション実装
  • 再利用可能なコンポーザブルとデザインシステムの設計
  • リコンポジションとレンダリングパフォーマンスの最適化

状態管理

ViewModel + 単一状態オブジェクト

画面状態には単一のデータクラスを使用します。StateFlowとして公開し、Composeで収集します

data class ItemListState(
    val items: List<Item> = emptyList(),
    val isLoading: Boolean = false,
    val error: String? = null,
    val searchQuery: String = ""
)

class ItemListViewModel(
    private val getItems: GetItemsUseCase
) : ViewModel() {
    private val _state = MutableStateFlow(ItemListState())
    val state: StateFlow<ItemListState> = _state.asStateFlow()

    fun onSearch(query: String) {
        _state.update { it.copy(searchQuery = query) }
        loadItems(query)
    }

    private fun loadItems(query: String) {
        viewModelScope.launch {
            _state.update { it.copy(isLoading = true) }
            getItems(query).fold(
                onSuccess = { items -> _state.update { it.copy(items = items, isLoading = false) } },
                onFailure = { e -> _state.update { it.copy(error = e.message, isLoading = false) } }
            )
        }
    }
}

Composeでの状態収集

@Composable
fun ItemListScreen(viewModel: ItemListViewModel = koinViewModel()) {
    val state by viewModel.state.collectAsStateWithLifecycle()

    ItemListContent(
        state = state,
        onSearch = viewModel::onSearch
    )
}

@Composable
private fun ItemListContent(
    state: ItemListState,
    onSearch: (String) -> Unit
) {
    // ステートレスなコンポーザブル — プレビューとテストが容易
}

イベントシンクパターン

複雑な画面では、複数のコールバックラムダの代わりにイベント用のシールドインターフェースを使用します:

sealed interface ItemListEvent {
    data class Search(val query: String) : ItemListEvent
    data class Delete(val itemId: String) : ItemListEvent
    data object Refresh : ItemListEvent
}

// ViewModelの中
fun onEvent(event: ItemListEvent) {
    when (event) {
        is ItemListEvent.Search -> onSearch(event.query)
        is ItemListEvent.Delete -> deleteItem(event.itemId)
        is ItemListEvent.Refresh -> loadItems(_state.value.searchQuery)
    }
}

// コンポーザブルの中 — 多数ではなく単一ラムダ
ItemListContent(
    state = state,
    onEvent = viewModel::onEvent
)

ナビゲーション

型安全なナビゲーションCompose Navigation 2.8+

ルートを@Serializableオブジェクトとして定義します:

@Serializable data object HomeRoute
@Serializable data class DetailRoute(val id: String)
@Serializable data object SettingsRoute

@Composable
fun AppNavHost(navController: NavHostController = rememberNavController()) {
    NavHost(navController, startDestination = HomeRoute) {
        composable<HomeRoute> {
            HomeScreen(onNavigateToDetail = { id -> navController.navigate(DetailRoute(id)) })
        }
        composable<DetailRoute> { backStackEntry ->
            val route = backStackEntry.toRoute<DetailRoute>()
            DetailScreen(id = route.id)
        }
        composable<SettingsRoute> { SettingsScreen() }
    }
}

ダイアログとボトムシートナビゲーション

命令型のshow/hideの代わりにdialog()とオーバーレイパターンを使用します:

NavHost(navController, startDestination = HomeRoute) {
    composable<HomeRoute> { /* ... */ }
    dialog<ConfirmDeleteRoute> { backStackEntry ->
        val route = backStackEntry.toRoute<ConfirmDeleteRoute>()
        ConfirmDeleteDialog(
            itemId = route.itemId,
            onConfirm = { navController.popBackStack() },
            onDismiss = { navController.popBackStack() }
        )
    }
}

コンポーザブル設計

スロットベースのAPI

柔軟性のためにスロットパラメータを持つコンポーザブルを設計します:

@Composable
fun AppCard(
    modifier: Modifier = Modifier,
    header: @Composable () -> Unit = {},
    content: @Composable ColumnScope.() -> Unit,
    actions: @Composable RowScope.() -> Unit = {}
) {
    Card(modifier = modifier) {
        Column {
            header()
            Column(content = content)
            Row(horizontalArrangement = Arrangement.End, content = actions)
        }
    }
}

Modifier順序

Modifierの順序は重要です — 以下の順序で適用します:

Text(
    text = "Hello",
    modifier = Modifier
        .padding(16.dp)          // 1. レイアウト(パディング、サイズ)
        .clip(RoundedCornerShape(8.dp))  // 2. 形状
        .background(Color.White) // 3. 描画(背景、ボーダー)
        .clickable { }           // 4. インタラクション
)

KMPプラットフォーム固有のUI

プラットフォームコンポーザブルのexpect/actual

// commonMain
@Composable
expect fun PlatformStatusBar(darkIcons: Boolean)

// androidMain
@Composable
actual fun PlatformStatusBar(darkIcons: Boolean) {
    val systemUiController = rememberSystemUiController()
    SideEffect { systemUiController.setStatusBarColor(Color.Transparent, darkIcons) }
}

// iosMain
@Composable
actual fun PlatformStatusBar(darkIcons: Boolean) {
    // iOSはUIKitインターロップまたはInfo.plistで処理
}

パフォーマンス

スキップ可能なリコンポジションのための安定した型

すべてのプロパティが安定している場合、クラスを@Stableまたは@Immutableでマークします:

@Immutable
data class ItemUiModel(
    val id: String,
    val title: String,
    val description: String,
    val progress: Float
)

key()と遅延リストの正しい使用

LazyColumn {
    items(
        items = items,
        key = { it.id }  // 安定したキーによりアイテムの再利用とアニメーションが可能
    ) { item ->
        ItemRow(item = item)
    }
}

derivedStateOfで読み取りを遅延

val listState = rememberLazyListState()
val showScrollToTop by remember {
    derivedStateOf { listState.firstVisibleItemIndex > 5 }
}

リコンポジションでのアロケーションを避ける

// 悪い例 — リコンポジションのたびに新しいラムダとリストが作られる
items.filter { it.isActive }.forEach { ActiveItem(it, onClick = { handle(it) }) }

// 良い例 — 各アイテムにキーを付けてコールバックが正しい行に紐づくようにする
val activeItems = remember(items) { items.filter { it.isActive } }
activeItems.forEach { item ->
    key(item.id) {
        ActiveItem(item, onClick = { handle(item) })
    }
}

テーマ設定

Material 3ダイナミックテーマ

@Composable
fun AppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            if (darkTheme) dynamicDarkColorScheme(LocalContext.current)
            else dynamicLightColorScheme(LocalContext.current)
        }
        darkTheme -> darkColorScheme()
        else -> lightColorScheme()
    }

    MaterialTheme(colorScheme = colorScheme, content = content)
}

避けるべきアンチパターン

  • ライフサイクルに対してより安全なcollectAsStateWithLifecycleを使用したMutableStateFlowがある場合にViewModelでmutableStateOfを使用すること
  • コンポーザブルの深い階層にNavControllerを渡すこと — 代わりにラムダコールバックを渡す
  • @Composable関数内の重い計算 — ViewModelかremember {}に移動する
  • 一部の設定では設定変更のたびに再実行されるため、ViewModel initの代替としてLaunchedEffect(Unit)を使用すること
  • コンポーザブルのパラメータに新しいオブジェクトインスタンスを作成すること — 不必要なリコンポジションを引き起こす

参照

スキル: モジュール構造とレイヤーについてはandroid-clean-architectureを参照。 スキル: コルーチンとFlowパターンについてはkotlin-coroutines-flowsを参照。