ショウのプログラミング教室

【2025年版】Jetpack Compose入門完全ガイド!Kotlinで始めるモダンなAndroid UI開発

AndroidアプリのUI開発が大きく変わろうとしています。Googleが推奨する次世代のUIツールキット「Jetpack Compose」をご存知でしょうか。

従来のXMLレイアウトと比べて、コードがシンプルになり、開発効率が大幅に向上します。この記事では、Jetpack Composeの基礎から実践的な使い方まで、初心者にもわかりやすく解説します。

Jetpack Composeとは?従来のXMLとの違い

Jetpack Composeは、Kotlinで宣言的にUIを構築できる、Androidの最新UIツールキットです。2021年に正式版がリリースされ、現在ではAndroid開発の標準となりつつあります。

従来のXML方式との主な違い

XMLレイアウトでは、レイアウトファイル(XML)とロジック(Kotlin/Java)が分離していました。これに対してJetpack Composeでは、UIもロジックもKotlinコードで記述します。これにより、コードの見通しが良くなり、保守性が向上します。

XMLではfindViewById()DataBindingを使ってビューを操作していましたが、Composeでは状態管理が自動化されており、状態が変わると自動的にUIが更新されます(リコンポジション)。

また、XMLではネストが深くなりがちで可読性が低下しましたが、Composeでは関数の組み合わせで表現するため、コードがシンプルになります。

開発環境のセットアップ

Jetpack Composeを使うには、Android Studio Arctic Fox(2020.3.1)以降が必要です。最新版のAndroid Studioを使用することをおすすめします。

新規プロジェクトを作成する際、テンプレートから「Empty Activity」を選び、「Build configuration language」でKotlin DSLを選択します。

build.gradle.kts(Module)に以下の依存関係が含まれていることを確認しましょう。

android {
    compileSdk = 34
    
    defaultConfig {
        minSdk = 21
        targetSdk = 34
    }
    
    buildFeatures {
        compose = true
    }
    
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.8"
    }
    
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

dependencies {
    val composeBom = platform("androidx.compose:compose-bom:2024.02.00")
    implementation(composeBom)
    
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.material3:material3")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.activity:activity-compose:1.8.2")
    
    debugImplementation("androidx.compose.ui:ui-tooling")
    debugImplementation("androidx.compose.ui:ui-test-manifest")
}

基本の書き方:初めてのComposable関数

Jetpack ComposeではUIを「Composable関数」として定義します。@Composableアノテーションを付けた関数がUIコンポーネントになります。

import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun Greeting(name: String) {
    Text(text = "こんにちは、${name}さん!")
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    Greeting(name = "太郎")
}

@Previewアノテーションを付けることで、Android Studioのプレビュー機能でUIを確認できます。コードを書きながらリアルタイムでUIを確認できるため、開発効率が大幅に向上します。

レイアウトの基本:Column、Row、Box

Jetpack Composeでは、レイアウトを構成する基本的なコンポーネントとして、Column(縦並び)、Row(横並び)、Box(重ね合わせ)があります。

import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun LayoutExample() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        Text("Column: 縦に並べる")
        
        Row(
            horizontalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Button(onClick = { }) { Text("ボタン1") }
            Button(onClick = { }) { Text("ボタン2") }
            Button(onClick = { }) { Text("ボタン3") }
        }
        
        Box(
            modifier = Modifier
                .size(200.dp)
                .padding(8.dp),
            contentAlignment = Alignment.Center
        ) {
            Text("Boxの中央")
        }
    }
}

Modifierを使うことで、サイズ、パディング、配置などのスタイルを柔軟に設定できます。Modifierは連鎖的に適用され、記述順序が重要です。

状態管理:remember と mutableStateOf

Jetpack Composeの最大の特徴は、状態管理の仕組みです。状態が変わると自動的にUIが再構築(リコンポジション)されます。

import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun CounterApp() {
    var count by remember { mutableStateOf(0) }
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "カウント: $count",
            style = MaterialTheme.typography.headlineMedium
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Row(
            horizontalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Button(onClick = { count++ }) {
                Text("増やす")
            }
            Button(onClick = { count-- }) {
                Text("減らす")
            }
            Button(onClick = { count = 0 }) {
                Text("リセット")
            }
        }
    }
}

rememberは、リコンポジション時に値を保持するために使います。mutableStateOfは、変更可能な状態を作成します。byキーワードを使うことで、値の取得と設定が簡潔に書けます。

リストの表示:LazyColumnとLazyRow

長いリストを表示する場合、LazyColumnLazyRowを使います。これらは必要な項目だけを描画するため、パフォーマンスが優れています。

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

data class Task(val id: Int, val title: String, val completed: Boolean)

@Composable
fun TaskList() {
    val tasks = remember {
        mutableStateListOf(
            Task(1, "買い物に行く", false),
            Task(2, "掃除をする", true),
            Task(3, "レポートを書く", false),
            Task(4, "プログラミング学習", false),
            Task(5, "運動をする", true)
        )
    }
    
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(tasks) { task ->
            TaskItem(task = task)
        }
    }
}

@Composable
fun TaskItem(task: Task) {
    Card(
        modifier = Modifier.fillMaxWidth()
    ) {
        Row(
            modifier = Modifier.padding(16.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Checkbox(
                checked = task.completed,
                onCheckedChange = { }
            )
            Spacer(modifier = Modifier.width(8.dp))
            Text(
                text = task.title,
                style = MaterialTheme.typography.bodyLarge
            )
        }
    }
}

items関数を使うことで、リストの各要素に対してComposable関数を呼び出せます。キーを指定することで、リストの更新時のパフォーマンスを最適化できます。

ナビゲーション:画面遷移の実装

複数の画面を持つアプリでは、Jetpack Compose Navigationを使います。

まず、依存関係を追加します。

dependencies {
    implementation("androidx.navigation:navigation-compose:2.7.6")
}

次に、ナビゲーションを実装します。

import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController

@Composable
fun MyApp() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = "home"
    ) {
        composable("home") {
            HomeScreen(navController)
        }
        composable("detail/{itemId}") { backStackEntry ->
            val itemId = backStackEntry.arguments?.getString("itemId")
            DetailScreen(navController, itemId)
        }
    }
}

@Composable
fun HomeScreen(navController: NavHostController) {
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text("ホーム画面", style = MaterialTheme.typography.headlineMedium)
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { navController.navigate("detail/123") }) {
            Text("詳細画面へ")
        }
    }
}

@Composable
fun DetailScreen(navController: NavHostController, itemId: String?) {
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text("詳細画面", style = MaterialTheme.typography.headlineMedium)
        Text("Item ID: $itemId")
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { navController.popBackStack() }) {
            Text("戻る")
        }
    }
}

NavHostでルーティングを定義し、navController.navigate()で画面遷移を行います。パラメータを渡すこともでき、動的なルーティングが可能です。

Material Design 3の活用

Jetpack ComposeはMaterial Design 3を標準でサポートしています。一貫性のある美しいUIを簡単に構築できます。

import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Material3Example() {
    var text by remember { mutableStateOf("") }
    var expanded by remember { mutableStateOf(false) }
    val options = listOf("オプション1", "オプション2", "オプション3")
    
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("Material Design 3") },
                colors = TopAppBarDefaults.topAppBarColors(
                    containerColor = MaterialTheme.colorScheme.primaryContainer
                )
            )
        },
        floatingActionButton = {
            FloatingActionButton(onClick = { }) {
                Text("+")
            }
        }
    ) { paddingValues ->
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(paddingValues)
                .padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            OutlinedTextField(
                value = text,
                onValueChange = { text = it },
                label = { Text("テキスト入力") },
                modifier = Modifier.fillMaxWidth()
            )
            
            ExposedDropdownMenuBox(
                expanded = expanded,
                onExpandedChange = { expanded = it }
            ) {
                OutlinedTextField(
                    value = options.firstOrNull() ?: "",
                    onValueChange = { },
                    readOnly = true,
                    label = { Text("ドロップダウン") },
                    modifier = Modifier
                        .menuAnchor()
                        .fillMaxWidth()
                )
                ExposedDropdownMenu(
                    expanded = expanded,
                    onDismissRequest = { expanded = false }
                ) {
                    options.forEach { option ->
                        DropdownMenuItem(
                            text = { Text(option) },
                            onClick = { expanded = false }
                        )
                    }
                }
            }
            
            Card(
                modifier = Modifier.fillMaxWidth()
            ) {
                Column(
                    modifier = Modifier.padding(16.dp)
                ) {
                    Text(
                        "カードコンポーネント",
                        style = MaterialTheme.typography.titleLarge
                    )
                    Text(
                        "Material Design 3のカードです",
                        style = MaterialTheme.typography.bodyMedium
                    )
                }
            }
        }
    }
}

Scaffoldを使うことで、TopAppBar、FloatingActionButton、BottomNavigationなどを簡単に配置できます。Material Design 3のコンポーネントは、自動的にテーマカラーを適用します。

ViewModelとの連携

実際のアプリでは、ViewModelを使ってビジネスロジックと状態を管理します。

依存関係を追加します。

dependencies {
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
}

ViewModelを作成します。

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

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

class MyViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
    
    init {
        loadItems()
    }
    
    private fun loadItems() {
        viewModelScope.launch {
            _uiState.value = _uiState.value.copy(isLoading = true)
            try {
                // データ取得処理(例:APIコール)
                val items = listOf("アイテム1", "アイテム2", "アイテム3")
                _uiState.value = _uiState.value.copy(
                    items = items,
                    isLoading = false
                )
            } catch (e: Exception) {
                _uiState.value = _uiState.value.copy(
                    error = e.message,
                    isLoading = false
                )
            }
        }
    }
    
    fun addItem(item: String) {
        val currentItems = _uiState.value.items
        _uiState.value = _uiState.value.copy(items = currentItems + item)
    }
}

ComposeからViewModelを使います。

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    
    Column(modifier = Modifier.fillMaxSize()) {
        when {
            uiState.isLoading -> {
                Box(
                    modifier = Modifier.fillMaxSize(),
                    contentAlignment = androidx.compose.ui.Alignment.Center
                ) {
                    CircularProgressIndicator()
                }
            }
            uiState.error != null -> {
                Text("エラー: ${uiState.error}")
            }
            else -> {
                LazyColumn(
                    contentPadding = PaddingValues(16.dp),
                    verticalArrangement = Arrangement.spacedBy(8.dp)
                ) {
                    items(uiState.items) { item ->
                        Card(modifier = Modifier.fillMaxWidth()) {
                            Text(
                                text = item,
                                modifier = Modifier.padding(16.dp)
                            )
                        }
                    }
                }
            }
        }
    }
}

collectAsStateWithLifecycle()を使うことで、StateFlowの値をComposeの状態として扱えます。ライフサイクルを考慮した収集が自動的に行われます。

パフォーマンス最適化のポイント

Jetpack Composeでパフォーマンスの良いアプリを作るためのポイントを紹介します。

不必要なリComposeを避ける

状態が変わると、その状態を使用しているComposable関数が再実行されます。不必要な再実行を避けるため、状態のスコープを適切に設定しましょう。

// 悪い例:全体が再実行される
@Composable
fun BadExample() {
    var count by remember { mutableStateOf(0) }
    Column {
        Text("タイトル") // countが変わるたびに再実行される
        Button(onClick = { count++ }) {
            Text("Count: $count")
        }
    }
}

// 良い例:必要な部分だけ再実行される
@Composable
fun GoodExample() {
    var count by remember { mutableStateOf(0) }
    Column {
        Text("タイトル") // 再実行されない
        CounterButton(count) { count++ }
    }
}

@Composable
fun CounterButton(count: Int, onClick: () -> Unit) {
    Button(onClick = onClick) {
        Text("Count: $count")
    }
}

derivedStateOfの活用

計算コストの高い処理はderivedStateOfを使って最適化できます。

@Composable
fun OptimizedList() {
    val items by remember { mutableStateOf(List(100) { it }) }
    val filteredItems by remember {
        derivedStateOf {
            items.filter { it % 2 == 0 } // 偶数のみ
        }
    }
    
    LazyColumn {
        items(filteredItems) { item ->
            Text("Item: $item")
        }
    }
}

LazyListのキーを指定

リストアイテムにキーを指定することで、更新時のパフォーマンスが向上します。

LazyColumn {
    items(
        items = taskList,
        key = { task -> task.id }
    ) { task ->
        TaskItem(task)
    }
}

まとめ

Jetpack Composeは、Androidアプリ開発を大きく進化させる技術です。この記事で解説した内容をまとめます。

Jetpack Composeでは、UIをKotlinコードで宣言的に記述でき、XMLレイアウトよりシンプルで保守性が高くなります。状態管理が自動化されており、状態が変わると自動的にUIが更新されます。Column、Row、Boxなどの基本コンポーネントを組み合わせてレイアウトを構築し、LazyColumnやLazyRowで効率的にリストを表示できます。NavigationやViewModelとの連携も簡単で、Material Design 3を標準サポートしています。

Jetpack Composeは学習コストがありますが、一度慣れると従来の方法より圧倒的に効率的に開発できます。ぜひあなたも次のプロジェクトでJetpack Composeを試してみてください。


この記事が役に立ちましたら、ぜひシェアしてください。質問やコメントもお待ちしています!

モバイルバージョンを終了