※ChatGPTを使用して記事を作成しています。
Jetpack Composeを使い始めたばかりの頃、多くの開発者がこう考えます。
「Composableは関数なんだから、簡単に再利用できるはず」
しかし実際に開発を進めると、すぐに壁にぶつかります。
- 別画面で使おうとしたら依存が多すぎる
- ViewModelに依存していて使い回せない
- 状態管理が複雑で他画面で使えない
そして気づきます。
「Composableは書くだけでは再利用できない」
今回は
Composeで再利用可能なUIを作る設計パターン
について整理します。
なぜComposableは再利用しづらくなるのか
Compose初心者が最初に書きがちなコードがあります。
@Composable
fun UserProfile(viewModel: UserViewModel) {
val user by viewModel.user.collectAsState()
Column {
Text(user.name)
Button(onClick = {
viewModel.followUser()
}) {
Text("Follow")
}
}
}
一見すると問題なさそうです。
しかしこのComposableは 再利用できません。
なぜなら、
- ViewModelに依存
- 状態取得を内部で実行
- ロジックが埋め込まれている
からです。
このUIは UserViewModel専用UI になっています。
つまり、
UI = ViewModel + 状態 + ロジック
がすべて混ざっています。
これでは再利用できません。
再利用できるUIの基本原則
Composeで再利用可能なUIを作るときの原則はシンプルです。
UIは状態とイベントだけを受け取る
つまり、
Composable = UI + State + Event
です。
ViewModelやRepositoryは登場しません。
再利用可能なUIの書き方
先ほどのコードを改善します。
@Composable
fun UserProfile(
userName: String,
onFollowClick: () -> Unit
) {
Column {
Text(userName)
Button(onClick = onFollowClick) {
Text("Follow")
}
}
}
これでどうなるでしょうか。
このUIは
- ViewModel不要
- Repository不要
- 状態管理不要
になります。
つまり
- どの画面でも使える
- Previewで使える
- テスト可能
になります。
ViewModelはどこに行ったのか
ViewModelは 呼び出し側 に置きます。
@Composable
fun UserProfileScreen(
viewModel: UserViewModel = viewModel()
) {
val user by viewModel.user.collectAsState()
UserProfile(
userName = user.name,
onFollowClick = {
viewModel.followUser()
}
)
}
ここで役割が分離されています。
Screen → 状態取得
UI → 表示のみ
この構造は Composeの設計パターンの基本 です。
さらに再利用性を高めるテクニック
ここからが実務で重要なポイントです。
再利用できるUIには 3つのレベル があります。
レベル1:データのみ受け取るUI
最もシンプルな形です。
@Composable
fun UserName(name: String) {
Text(name)
}
これは完全に再利用できます。
ただし、
現実のUIはもっと複雑です。
レベル2:UI Stateを受け取る
Composeでは UI Stateをまとめて渡す設計 がよく使われます。
data class UserProfileUiState(
val name: String,
val isFollowing: Boolean
)
Composableはこれを受け取ります。
@Composable
fun UserProfile(
state: UserProfileUiState,
onFollowClick: () -> Unit
) {
Column {
Text(state.name)
Button(onClick = onFollowClick) {
Text(
if (state.isFollowing) "Following"
else "Follow"
)
}
}
}
メリットは大きいです。
- 引数が整理される
- UI状態が一目でわかる
- 拡張しやすい
レベル3:Slot API
Composeの強力な再利用パターンが Slot API です。
例えば Scaffold はこの構造です。
Scaffold(
topBar = { TopAppBar(...) },
content = { ... }
)
自分でも同じことができます。
@Composable
fun ProfileCard(
header: @Composable () -> Unit,
content: @Composable () -> Unit
) {
Card {
header()
Spacer(Modifier.height(8.dp))
content()
}
}
使用例
ProfileCard(
header = {
Text("User Profile")
},
content = {
Text("Profile Content")
}
)
これにより
UI構造を再利用しながら中身を差し替える
ことができます。
再利用UIを壊すアンチパターン
実務でよくある失敗を紹介します。
アンチパターン① ViewModel依存
@Composable
fun UserCard(viewModel: UserViewModel)
これは再利用不能です。
アンチパターン② collectAsState inside UI
val user by viewModel.user.collectAsState()
UIの責務ではありません。
アンチパターン③ MutableStateを渡す
@Composable
fun UserName(name: MutableState<String>)
これは危険です。
UIが 状態を書き換えられる ようになります。
推奨は 値 + イベント です。
Compose再利用設計の黄金ルール
実務で役立つルールをまとめます。
① UIはViewModelを知らない
Composable → ViewModel禁止
② UIはStateだけ受け取る
Composable(state, event)
③ 状態取得はScreenが担当
Screen → ViewModel
UI → 描画
なぜComposeは再利用設計が重要なのか
従来のXML UIでは、
View + findViewById
という構造でした。
UIは再利用より 画面単位設計 でした。
しかしComposeは違います。
UI = 関数
だからこそ、
設計しないと再利用できない
という世界になりました。
まとめ
Composeで再利用可能なUIを作るためには、次の原則が重要です。
① UIは状態とイベントだけ受け取る
Composable(
state,
onEvent
)
② ViewModelはScreenに置く
Screen → ViewModel
UI → 描画
③ Slot APIで拡張可能にする
Composable(
header = {},
content = {}
)
Composeは「関数UI」です。
しかし、
関数だからといって自動的に再利用できるわけではありません。
再利用できるUIを作るためには、
- 状態設計
- 責務分離
- Slot API
といった 設計思想 が必要です。
そしてこの設計を理解すると、
Composeのコードは
驚くほどシンプルで拡張しやすくなる
のです。

