【書籍特別編】Jetpack Compose 開発シリーズ | 第3回 ボタンをタップできるようにしよう

💡 今回の内容
ボタンをクリックできるようにして、アプリを仕上げていきましょう。
📚 このシリーズについて
本シリーズでは、書籍『いきなりプログラミング Android アプリ開発』の第1章のアプリを Jetpack Compose で開発していく手順を紹介します。
📎 メニュー
✅ 第0回:イントロダクション
✅ 第1回:プロジェクトの用意と Jetpack Compose の概要
✅ 第2回:アプリ画面を作ってみよう
➡️ 第3回:ボタンをタップできるようにしよう
ボタンのクリックイベント
📘 参考:p.50〜(書籍はこちら)
書籍と同じ方法で書くと?
まずは書籍と同じように「変数 count を用意して、ボタンをタップした回数を表示する」というコードを書いてみましょう。
AppScreen() にコードを書きます。
3行目のコードを追加、8行目をコメントアウトして9行目を追加、18行目 onClick 部分にコードを追加します。
@Composable
fun AppScreen() {
var count = 0
Column(
// 省略
) {
Text(
//text = stringResource(R.string.message),
text = "$count",
fontSize = 18.sp
)
Spacer(Modifier.height(40.dp))
Image(
// 省略
)
Spacer(Modifier.height(40.dp))
Button(
onClick = { count++ }
) {
Text(stringResource(R.string.btn_water))
「ボタンをタップしたら count に1を加算して Text() に表示」というコードにしました。
ボタンをタップしたら count が増えていくはずなので、動作を確認してみましょう。
インテラクティブモードの使い方
Jetpack Compose のプレビューには Interactive Mode(インテラクティブモード) という機能が用意されていて、エミュレータを起動せずにアプリ画面を操作できるようになっています。
これを使ってボタンをタップをしてみましょう。
プレビュー画面の右上にある︙をクリックして [Start Interactive Mode] を選択します。

[水をあげる] ボタンをタップしても、カウントが 0 から変わりませんね(理由はこのあと説明します)。

確認が終わったら [Stop Interactive Mode] で終了します。

なぜカウントが変わらない?
なぜボタンをタップしてもカウントが変わらなかったかというと、Jetpack Compose には「再コンポーズ」という仕組みがあるからです。
再コンポーズは、値が変更されたらコンポーザブル関数を再実行するという仕組みです「変数 count の値が変わる → 再度 AppScreen コンポーザブル関数を実行 → 画面が更新される」という順番でコードが実行されます。
ここで問題になるのが count の値が変わって AppScreen コンポーザブル関数が再実行されると、そのたびに count の値が 0 に戻ってしまうということです。これが原因で、ボタンをタップしても0が表示され続けていました。
値が0にリセットされないようにするには、変数 count の値を保持しておく必要があります。
コードを修正しましょう
再コンポーズしても count の値が保持されるようにコードを修正しましょう。
41行目あたりの
var count = 0
を
var count by remember { mutableIntStateOf(0) }
に変更します。
必要な import
by 部分に赤い波線が出るかもしれません。その場合は by 部分にカーソルを合わせると表示される [import operators ‘IntState.getValue’, ‘State.getValue’] をクリックします。

もう一度カーソルを合わせて、同じように青いメッセージを選択します。

必要な import 文が追加されました。正しく追加できないときは import 部分に直接コードを書いても OK です。

Remeber と State
remember は「覚える」、state は「状態」という意味で、状態(変数の値)を覚えておくためのコードです。
変数 count の値は int 型(数値)なので、mutableIntStateOf(0) で初期値が0の値を用意しています。
初期値なしの文字列の場合は
var text by remember { mutableStateOf("") }
と書きます。
Jetpack Compose では、何度も使うことになるコードです。ここで仕組みを説明すると長くなってしまうので「とりあえずこのように書く!」と覚えておきましょう。
プレビューを確認
もう一度インテラクティブモードを起動してボタンをタップしてみましょう。
今度はボタンをタップするたびにカウントが更新されましたね。

メッセージと画像を切り替える
📘 参考:p.52〜(書籍はこちら)
次はボタンがタップされた回数に合わせて、メッセージと画像を切り替えましょう。
変数の用意
count と同じようにメッセージと画像も remember と state で保持しておきます。
4・5行目のコードを追加します。
@Composable
fun AppScreen() {
var count by remember { mutableIntStateOf(0) }
var messageId by remember { mutableIntStateOf(R.string.message) }
var imageId by remember { mutableIntStateOf(R.drawable.f0) }
Column(
modifier = Modifier.fillMaxSize(),
メッセージと画像は id を使って取り出します。id は int 型(数値)なので、ここでも mutableIntStateOf() を使っています。
この変数を使って Text() と Image() に値をセットしましょう。
11・16行目のようにコードを変更します。
@Composable
fun AppScreen() {
var count by remember { mutableIntStateOf(0) }
var messageId by remember { mutableIntStateOf(R.string.message) }
var imageId by remember { mutableIntStateOf(R.drawable.f0) }
Column(
// 省略
) {
Text(
text = stringResource(messageId),
fontSize = 18.sp
)
Spacer(Modifier.height(40.dp))
Image(
painter = painterResource(imageId),
contentDescription = null,
modifier = Modifier.size(200.dp)
)
メッセージと画像を決める
次に、変数 count の値によって表示するメッセージと画像を決めるコードを書きます。
書籍と同じように when を使って判定します。細かい条件分けについては書籍 p.52 をご確認ください。
7〜36行目のコードを追加します。
@Composable
fun AppScreen() {
var count by remember { mutableIntStateOf(0) }
var messageId by remember { mutableIntStateOf(R.string.message) }
var imageId by remember { mutableIntStateOf(R.drawable.f0) }
when (count) {
0 -> {
messageId = R.string.message
imageId = R.drawable.f0
}
in 1..19 -> {
messageId = R.string.message0
imageId = R.drawable.f0
}
in 20..39 -> {
messageId = R.string.message1
imageId = R.drawable.f1
}
in 40..59 -> {
messageId = R.string.message2
imageId = R.drawable.f2
}
in 60..79 -> {
messageId = R.string.message3
imageId = R.drawable.f3
}
in 80..99 -> {
messageId = R.string.message4
imageId = R.drawable.f4
}
else -> {
messageId = R.string.message5
imageId = R.drawable.f5
}
}
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
動作確認
インテラクティブモードを起動してボタンをタップしてみましょう。
ボタンをタップした回数に合わせて、メッセージと画像が切り替われば成功です!
* 変化がわかりやすいように、以下の画像では count++ ではなく count += 10 として実行しています。

ボタンの仕上げ
ボタンの表示・非表示を切り替える
📘 参考:p.55(書籍はこちら)
ボタンを間違えてタップしないように、「リセット」ボタンは花が咲くまで非表示にしておきましょう。
2つのボタンそれぞれに、3・10行目のコードを追加します。
Button(
onClick = { count++ },
modifier = Modifier.alpha(if (count < 100) 1f else 0f)
) {
Text(stringResource(R.string.btn_water))
}
Spacer(Modifier.height(20.dp))
OutlinedButton(
onClick = {},
modifier = Modifier.alpha(if (count < 100) 0f else 1f)
) {
Text(stringResource(R.string.btn_reset))
}
alpha は透過度を指定するもので
- 0f → 透過(見えない)
- 1f → 非透過(見える)
という状態にできます。
[水をやる] ボタンは、count が 100 未満のときは表示、それ以外は非表示としています。
反対に [リセット] ボタンは 、count が 100 未満のときは非表示、それ以外は表示としています。

リセットボタンの実装
📘 参考:p.57(書籍はこちら)
最後に [リセット] ボタンをタップした時の処理です。
count を 0 に戻すだけで、最初の状態に戻すことができます(2行目)。
OutlinedButton(
onClick = { count = 0 },
modifier = Modifier.alpha(if (count < 100) 0f else 1f)
) {
Text(stringResource(R.string.btn_reset))
}
エミュレータで確認
最後にエミュレータでもアプリの動作を確認しておきます。
エミュレータにアプリ画面を表示するためには onCreate メソッドから AppScreen() を忘れずに呼び出しましょう(7行目)。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
FlowerComposeTheme {
AppScreen()
}
}
}
}
アプリを実行できれば完成です!
* 変化がわかりやすいように、以下の画像では count++ ではなく count += 10 として実行しています。

完成
最後までお読みいただきありがとうございました!
XML よりも簡潔なコードを書くことができましたね。簡単なアプリでしたが Jetpack Compose の雰囲気を少し知っていただけたのではないかと思います。
Jetpack Compose の特徴の1つは「状態(値)を保持すること」です。毎回 remember と state を使って値を保持するのは面倒に見えますが、値を変更すると自動的に UI も更新されるので、予期せぬエラーやミスを防ぐことができるというメリットもあります。
今後は Jetpack Compose での開発が主流になっていくことが考えられるので、ぜひ簡単なコードから挑戦してみてください。
当サイトでも Jetpack Compose に関する記事や講座を準備中です。ご興味のある方はぜひお知らせメールにご登録ください✉️
完成コード
- MainActivity.kt
-
package com.example.flowercompose import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.material3.Button import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.example.flowercompose.ui.theme.FlowerComposeTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { FlowerComposeTheme { AppScreen() } } } } @Composable fun AppScreen() { var count by remember { mutableIntStateOf(0) } var messageId by remember { mutableIntStateOf(R.string.message) } var imageId by remember { mutableIntStateOf(R.drawable.f0) } when (count) { 0 -> { messageId = R.string.message imageId = R.drawable.f0 } in 1..19 -> { messageId = R.string.message0 imageId = R.drawable.f0 } in 20..39 -> { messageId = R.string.message1 imageId = R.drawable.f1 } in 40..59 -> { messageId = R.string.message2 imageId = R.drawable.f2 } in 60..79 -> { messageId = R.string.message3 imageId = R.drawable.f3 } in 80..99 -> { messageId = R.string.message4 imageId = R.drawable.f4 } else -> { messageId = R.string.message5 imageId = R.drawable.f5 } } Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = stringResource(messageId), fontSize = 18.sp ) Spacer(Modifier.height(40.dp)) Image( painter = painterResource(imageId), contentDescription = null, modifier = Modifier.size(200.dp) ) Spacer(Modifier.height(40.dp)) Button( onClick = { count++ }, modifier = Modifier.alpha(if (count < 100) 1f else 0f) ) { Text(stringResource(R.string.btn_water)) } Spacer(Modifier.height(20.dp)) OutlinedButton( onClick = { count = 0 }, modifier = Modifier.alpha(if (count < 100) 0f else 1f) ) { Text(stringResource(R.string.btn_reset)) } } } @Preview(showBackground = true) @Composable private fun AppScreenPreview() { AppScreen() }
書籍の紹介
書籍では、フラワーシュミレータを含めた全6種類のアプリの作り方を学ぶことができます。(Jetpack Compose ではなく XML による開発です)
「コードを書く→アプリを動かす→コードを書く…」という手順を繰り返すことで、途中で挫折しにくい構成になっています。
とにかく動作するアプリを作ってみたい方、難しい参考書で挫折しまった方にオススメです!