はじめに
こんにちは!Android開発を長年やってきて、UIの進化を肌で感じている筆者です。最近、Jetpack Composeの普及により、Android UI開発の風景が大きく変わりました。
従来のXMLベースのレイアウトシステムから、宣言的UIパラダイムへの移行は、まさに革命的です。私自身、最初は「また新しい技術か...」と思っていましたが、実際に使ってみると、その効率性と直感性に驚かされました。
本記事では、Jetpack Composeの基本から実践的な使い方まで、実際のプロジェクトで得た知見を交えながら解説していきます。特に、2025年時点での最新機能やベストプラクティスも含めているので、これからComposeを始める方にとって実用的なガイドになると思います。
Jetpack Compose とは何か?
まず、Jetpack Composeについて簡単に説明しておきます。
Composeは、Googleが開発したAndroidの最新UIツールキットで、宣言的UI開発を採用しています。従来のXMLベースの命令的UIプログラミングとは根本的に異なるアプローチを取っています。
私が最初にComposeに触れたとき、一番印象的だったのは「UIの状態管理がこんなに簡単になるのか」ということでした。
主な特徴
🎯 宣言的アプローチ
従来の「どうやってUIを変更するか」ではなく、「UIがどのような状態であるべきか」を記述します。これにより、コードがよりシンプルで理解しやすくなります。
🔧 Kotlinファースト
Kotlinの機能(高階関数、ラムダ、コルーチンなど)を最大限活用して設計されています。
⚡ リアクティブ
データの変更に応じて自動的にUIを更新します。状態管理が格段に楽になりました。
🧩 コンポーザブル関数
再利用可能なUI要素を関数として簡単に作成できます。コンポーネント化が自然に行えます。
セットアップ方法
実際にComposeを使い始めるための手順を説明します。私は複数のプロジェクトでComposeを導入してきましたが、セットアップ自体は非常にシンプルです。
プロジェクト作成
最新のAndroid Studioを使えば、Composeプロジェクトの作成は簡単です:
- Android Studioを開く
- Start a new Android Studio projectを選択
- Empty Activity(Compose用のテンプレート)を選択
- 最小SDKをAPI level 21以上に設定
💡 ポイント: 新規プロジェクトであれば、最初からCompose対応のテンプレートを選ぶのがおすすめです。
基本的な使い方
さて、実際にComposeでUIを作ってみましょう。最初は戸惑うかもしれませんが、慣れてしまえば従来の方法よりもずっと楽になります。
最初のComposable関数
Composable関数は、@Composableアノテーションを付けた関数で、UIの構成要素を定義します。私が最初に学んだときも、この概念が一番重要だと感じました。
@Composable
fun MessageCard(name: String) {
Text(text = "Hello $name!")
}
// プレビュー機能
@Preview
@Composable
fun PreviewMessageCard() {
MessageCard("Android")
}
💡 ポイント: @Previewアノテーションを使うと、Android Studioでリアルタイムにプレビューが確認できます。これにより、開発効率が大幅に向上しました。
レイアウト
Composeのレイアウトシステムは、直感的で理解しやすいです。主要な3つのレイアウトコンポーネントを見てみましょう。
Column(縦並び)
要素を縦に並べるときに使います。LinearLayoutのandroid:orientation="vertical"に相当します。
@Composable
fun ColumnExample() {
Column {
Text("最初のテキスト")
Text("二番目のテキスト")
Text("三番目のテキスト")
}
}
Row(横並び)
要素を横に並べるときに使います。LinearLayoutのandroid:orientation="horizontal"に相当します。
@Composable
fun RowExample() {
Row {
Text("左")
Text("中央")
Text("右")
}
}
Box(重ね合わせ)
要素を重ね合わせるときに使います。FrameLayoutに似ていますが、より柔軟です。
@Composable
fun BoxExample() {
Box {
Text("背景テキスト")
Text("前面テキスト")
}
}
個人的な感想: 最初はこのシンプルさに驚きました。XMLで複雑なレイアウトを組んでいた時代が懐かしく感じます。
Modifier
Modifierは、Composeにおけるスタイリングの核心です。私が最初に理解するのに時間がかかった部分でもありますが、一度慣れると非常に強力なツールです。
Modifierは、Composableの見た目や動作をカスタマイズするための仕組みで、連結して使うことができます:
@Composable
fun StyledText() {
Text(
text = "スタイル付きテキスト",
modifier = Modifier
.background(Color.Blue)
.padding(16.dp)
.size(200.dp, 100.dp)
)
}
💡 ポイント: Modifierの順序は結果に影響します。paddingの前後でbackgroundを指定すると、見た目が変わります。これは最初によくハマるポイントです。
状態管理
ここからが、Composeの真骨頂です。状態管理について詳しく見ていきましょう。
remember と mutableStateOf
ComposeではrememberとmutableStateOfを使って状態を管理します。これは私がComposeを使い始めて最も感動した部分の一つです。従来のAndroid開発では、ビューの状態を管理するのに多くのボイラープレートコードが必要でした。Composeでは、状態が変わると自動的にUIが再描画(Recomposition)されます。
@Composable
fun ClickableText() {
var isClicked by remember { mutableStateOf(false) }
Text(
text = if (isClicked) "クリック済み" else "クリックしてください",
modifier = Modifier.clickable {
isClicked = !isClicked
}
)
}
状態のリフトアップ
これは非常に重要な概念です。状態を上位のComposableで管理し、下位に渡すパターンです。React経験者にはお馴染みの概念ですね。
// 状態を更新する上位Composable
@Composable
fun ParentComponent() {
var count by remember { mutableStateOf(0) }
Column {
Text("カウント: $count")
CounterButton(
count = count,
onCountChange = { count = it }
)
}
}
// 状態を参照する下位Composable
@Composable
fun CounterButton(
count: Int, // 上位から下位に値を渡す
onCountChange: (Int) -> Unit // 下位から上位に値を渡す
) {
Button(onClick = {
onCountChange(count + 1)
}) {
Text("増加")
}
}
💡 ポイント: 複数のコンポーネントで同じ状態を共有する必要がある場合は、必ず状態をリフトアップしましょう。これにより、データの流れが一方向になり、デバッグが楽になります。
副作用
Composeにおける副作用は、Composable関数のスコープ外で実行される処理のことです。APIコール、データベースアクセス、タイマー処理などが該当します。私が初期の頃に最も混乱した部分でもありますが、適切に理解して使うことで強力な機能となります。
LaunchedEffect
コルーチンを使った非同期処理を行う際に使用します。指定したキーが変更されたときに実行されます。
@Composable
fun UserProfile(userId: String) {
var user by remember { mutableStateOf<User?>(null) }
var isLoading by remember { mutableStateOf(false) }
LaunchedEffect(userId) {
isLoading = true
try {
user = userRepository.getUser(userId)
} catch (e: Exception) {
// エラーハンドリング
} finally {
isLoading = false
}
}
if (isLoading) {
CircularProgressIndicator()
} else {
user?.let { user ->
Column {
Text("名前: ${user.name}")
Text("メール: ${user.email}")
}
}
}
}
💡 ポイント: LaunchedEffectは指定したキーが変更されるたびに実行されます。userIdが変わるとAPIを再コールします。
DisposableEffect
リソースの取得と解放が必要な処理で使用します。ライフサイクルに連動した処理に適しています。
@Composable
fun LocationTracker() {
var location by remember { mutableStateOf<Location?>(null) }
DisposableEffect(Unit) {
val locationManager = getLocationManager()
val listener = object : LocationListener {
override fun onLocationChanged(newLocation: Location) {
location = newLocation
}
}
locationManager.requestLocationUpdates(listener)
// Composableが破棄されるときに実行
onDispose {
locationManager.removeLocationUpdates(listener)
}
}
location?.let {
Text("緯度: ${it.latitude}, 経度: ${it.longitude}")
} ?: Text("位置情報を取得中...")
}
💡 ポイント: このパターンを使ってメモリリークを防ぐことが重要です。特にセンサーやネットワークリスナーの管理で活用しています。
SideEffect
Compose以外の世界に状態を伝播させたいときに使用します。すべてのRecompositionで実行されます。
@Composable
fun AnalyticsLogger(screenName: String) {
SideEffect {
// 画面表示のたびにアナリティクスに送信
Analytics.logScreenView(screenName)
}
}
副作用使用時の注意点
副作用を使う際は以下の点に注意が必要です:
-
適切な副作用を選択する:
- 一回限りの処理:
LaunchedEffect - リソース管理:
DisposableEffect - 外部システムとの同期:
SideEffect
- 一回限りの処理:
-
キーの管理:
-
LaunchedEffectのキーは慎重に選ぶ - 不必要な再実行を避ける
-
-
メモリリーク対策:
-
DisposableEffectで必ずリソースを解放 - 長時間実行されるコルーチンの適切な管理
-
// 悪い例:無限にAPIコールが発生
@Composable
fun BadExample() {
var data by remember { mutableStateOf<String?>(null) }
LaunchedEffect(data) { // dataが変わるたびに実行される
data = fetchDataFromApi() // これによりdataが変わり、無限ループ
}
}
// 良い例:明確なキーを使用
@Composable
fun GoodExample(refreshTrigger: Long) {
var data by remember { mutableStateOf<String?>(null) }
LaunchedEffect(refreshTrigger) { // refreshTriggerが変わったときのみ実行
data = fetchDataFromApi()
}
}
💡 ポイント: 副作用は慎重に使用し、可能な限りテスタブルな状態管理パターンを採用することをおすすめします。ViewModelとの組み合わせで、より堅牢なアーキテクチャを構築できます。
Material Design 3 の活用
2025年現在、Material Design 3(Material You)がスタンダードです。私が過去に携わったプロジェクトでも、Material 3への移行でUXが大幅に改善しました。
テーマの設定
Material 3のテーマシステムは非常に洗練されています:
@Composable
fun MyApp() {
MaterialTheme {
// アプリのコンテンツ
MyAppContent()
}
}
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun MaterialComponentsExample() {
Column {
Button(onClick = { }) {
Text("ボタン")
}
OutlinedTextField(
value = "",
onValueChange = { },
label = { Text("入力フィールド") }
)
Card(
modifier = Modifier.padding(16.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
) {
Text(
text = "カードの内容",
modifier = Modifier.padding(16.dp)
)
}
}
}
まとめ
いかがでしたでしょうか?Jetpack Composeについて、基本から実践的な内容まで幅広く紹介させていただきました。
私自身、XML時代からComposeに移行して感じるのは、開発体験の劇的な向上です。特に以下の点で恩恵を感じています:
- 開発速度の向上: プレビュー機能とホットリロード
- コードの可読性: 宣言的な書き方でロジックが明確
- 保守性の改善: 状態管理がシンプルで追いやすい
- チーム開発: Kotlinの型安全性により、協業が楽