1. 始める前に
この Codelab では、ユーザーが Button コンポーザブルをタップしてサイコロを振る、インタラクティブな Dice Roller アプリを作成します。サイコロを振った結果は Image コンポーザブルで画面に表示されます。
Kotlin で Jetpack Compose を使用してアプリ レイアウトを作成し、Button コンポーザブルがタップされたときの動作を処理するビジネス ロジックを作成します。
前提条件
- Android Studio で基本的な Compose アプリを作成して実行できること。
- アプリで Textコンポーザブルを使用する方法を熟知していること。
- アプリの翻訳や文字列の再利用のために、テキストを文字列リソースに抽出する方法を理解していること。
- Kotlin プログラミングの基礎知識があること。
学習内容
- Compose を使用して Android アプリに Buttonコンポーザブルを追加する方法。
- Compose を使用して Android アプリの Buttonコンポーザブルに動作を追加する方法。
- Android アプリの Activityコードを開いて変更する方法。
作成するアプリの概要
- ユーザーにサイコロを振らせてその結果を表示する、Dice Roller というインタラクティブな Android アプリ。
必要なもの
- Android Studio がインストールされているパソコン
この Codelab が完了すると、アプリは次のようになります。

2. ベースラインを確立する
プロジェクトを作成する
- Android Studio で、[File] > [New] > [New Project] をクリックします。
- [New Project] ダイアログで [Empty Activity] を選択し、[Next] をクリックします。

- [Name] フィールドに「Dice Roller」と入力します。
- [Minimum SDK] フィールドでメニューから API レベル 24(Nougat)以上を選択し、[Finish] をクリックします。

3. レイアウト インフラストラクチャを作成する
プロジェクトをプレビューする
プロジェクトをプレビューするには:
- [Split] ペインまたは [Design] ペインで [Build & Refresh] をクリックします。

[Design] ペインにプレビューが表示されます。表示が小さくても、レイアウトを変更すると表示も変化するため心配する必要はありません。

サンプルコードを再構成する
Dice Roller アプリのテーマに近づけるために、生成されたコードの一部を変更する必要があります。
完成したアプリのスクリーンショットには、サイコロの画像と、サイコロを転がすためのボタンがありました。このアーキテクチャを反映するように、コンポーズ可能な関数を構成します。
サンプルコードを再構成するには:
- GreetingPreview()関数を削除します。
- @Composableアノテーションを付けて- DiceWithButtonAndImage()関数を作成します。
このコンポーズ可能な関数はレイアウトの UI コンポーネントを表し、また、ボタンクリックと画像表示のロジックを保持します。
- Greeting(name: String, modifier: Modifier = Modifier)関数を削除します。
- @Previewアノテーションと- @Composableアノテーションを付けて- DiceRollerApp()関数を作成します。
このアプリはボタンと画像のみで構成されているため、このコンポーズ可能な関数がアプリそのものだと考えてください。そのため、DiceRollerApp() 関数という名前になっています。
MainActivity.kt
@Preview
@Composable
fun DiceRollerApp() {
}
@Composable
fun DiceWithButtonAndImage() {
}
Greeting() 関数を削除したため、DiceRollerTheme() ラムダ本文の Greeting("Android") の呼び出しが赤色でハイライト表示されます。これは、コンパイラがその関数への参照を見つけられなくなったためです。
- onCreate()メソッドにある- setContent{}ラムダ内のコードをすべて削除します。
- setContent{}ラムダ本体で- DiceRollerTheme{}ラムダを呼び出し、- DiceRollerTheme{}ラムダ内で- DiceRollerApp()関数を呼び出します。
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        DiceRollerTheme {
            DiceRollerApp()
        }
    }
}
- DiceRollerApp()関数で、- DiceWithButtonAndImage()関数を呼び出します。
MainActivity.kt
@Preview
@Composable
fun DiceRollerApp() {
    DiceWithButtonAndImage()
}
修飾子を追加する
Compose は、Compose UI 要素の動作を装飾または変更する要素のコレクションである Modifier オブジェクトを使用します。これを使用して Dice Roller アプリのコンポーネントの UI コンポーネントをスタイル設定します。
修飾子を追加するには:
- Modifier型の- modifier引数を受け入れるように- DiceWithButtonAndImage()関数を変更し、デフォルト値- Modifierを代入します。
MainActivity.kt
@Composable 
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
}
このコード スニペットではわかりにくい可能性があるため、詳しく説明します。この関数では、modifier パラメータを渡すことができます。modifier パラメータのデフォルト値は Modifier オブジェクトであるため、メソッド シグネチャに = Modifier 部分があります。このパラメータのデフォルト値により、今後このメソッドを呼び出す場合、パラメータの値を渡すかどうかを決めることができます。独自の Modifier オブジェクトを渡せば、UI の動作と装飾をカスタマイズできます。Modifier オブジェクトを渡さない場合は、デフォルトの値(プレーンな Modifier オブジェクト)であるとみなされます。この手法はどのようなパラメータにも適用できます。デフォルトの引数の詳細については、Default arguments をご覧ください。
- DiceWithButtonAndImage()コンポーザブルに修飾子パラメータが設定されたため、コンポーザブルが呼び出されたときに修飾子を渡します。- DiceWithButtonAndImage()関数のメソッド シグネチャが変更されたため、呼び出されたときに、必要な装飾を行った- Modifierオブジェクトを渡す必要があります。- Modifierクラスは、- DiceRollerApp()関数でコンポーザブルの装飾(動作の追加)を行います。この場合、- DiceWithButtonAndImage()関数に渡す- Modifierオブジェクトに重要な装飾を追加します。
デフォルトがあるのに、どうしてわざわざ Modifier 引数を渡す必要があるのか、と思うかもしれません。これは、コンポーザブルが再コンポーズされる可能性があるためです。つまり実質、@Composable メソッドのコードブロックが再実行されます。Modifier オブジェクトがコードブロック内で作成された場合、再作成される可能性があるため、効率的ではありません。再コンポーズについては、この Codelab で後ほど説明します。
MainActivity.kt
DiceWithButtonAndImage(modifier = Modifier)
- Modifierオブジェクトに- fillMaxSize()メソッドを連結して、レイアウトが画面全体に表示されるようにします。
このメソッドは、利用可能なスペースをコンポーネントで埋めることを指定します。この Codelab ではこれまでに、完成した Dice Roller アプリの UI のスクリーンショットを確認しました。注目すべき特徴は、サイコロとボタンが画面の中央に配置されていることです。wrapContentSize() メソッドは、利用可能なスペースが少なくとも内部のコンポーネントと同じ大きさである必要があるということを指定します。ただし fillMaxSize() メソッドを使用しているため、利用可能なスペースよりレイアウト内部のコンポーネントが小さい場合は、Alignment オブジェクトを wrapContentSize() メソッドに渡して、利用可能なスペース内でコンポーネントをどのように配置するかを指定できます。
MainActivity.kt
DiceWithButtonAndImage(modifier = Modifier
    .fillMaxSize()
)
- wrapContentSize()メソッドを- Modifierオブジェクトに連結し、コンポーネントを中央に配置するための引数として- Alignment.Centerを渡します。- Alignment.Centerは、コンポーネントを縦方向と横方向の両方で中央に配置することを指定します。
MainActivity.kt
DiceWithButtonAndImage(modifier = Modifier
    .fillMaxSize()
    .wrapContentSize(Alignment.Center)
)
4. 縦向きレイアウトを作成する
Compose では、縦向きレイアウトは Column() 関数を使用して作成します。
Column() 関数は、子を縦方向に並べて配置するコンポーザブル レイアウトです。想定されるアプリデザインでは、次のようにサイコロの画像が [Roll] ボタンの上方に表示されます。

縦向きレイアウトを作成するには:
- DiceWithButtonAndImage()関数に- Column()関数を追加します。
- DiceWithButtonAndImage()メソッド シグネチャから- modifier引数を- Column()の修飾子引数に渡します。
modifier 引数により、Column() 関数内のコンポーザブルが、modifier インスタンスで呼び出される制約に従うようになります。
- horizontalAlignment引数を- Column()関数に渡し、- Alignment.CenterHorizontallyの値に設定します。
これにより、列内の子が幅に対してデバイス画面の中央に配置されます。
MainActivity.kt
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    Column (
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {}
}
5. ボタンを追加する
- strings.xmlファイルに文字列を追加し、- Roll値に設定します。
res/values/strings.xml
<string name="roll">Roll</string>
- Column()のラムダ本体に- Button()関数を追加します。
- MainActivity.ktファイルで、関数のラムダ本体の- Button()に- Text()関数を追加します。
- roll文字列の文字列リソース ID を- stringResource()関数に渡し、結果を- Textコンポーザブルに渡します。
MainActivity.kt
Column(
    modifier = modifier,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Button(onClick = { /*TODO*/ }) {
        Text(stringResource(R.string.roll))
    }
}
6. 画像を追加する
このアプリに不可欠なもう一つのコンポーネントは、ユーザーが [Roll] ボタンをタップすると結果を表示する、サイコロの画像です。Image コンポーザブルを使用して画像を追加しますが、画像リソースが必要です。そのため、まずはこのアプリのために用意された画像をダウンロードする必要があります。
サイコロの画像をダウンロードする
- こちらの URL を開いて、サイコロの画像を ZIP ファイル形式でパソコンにダウンロードします。ダウンロードが完了するまで待機します。
パソコンに保存したファイルを見つけます。通常は Downloads フォルダにあります。
- ZIP ファイルを解凍すると、1~6 の目を持つサイコロの画像ファイルが 6 つ入った、新しい dice_imagesフォルダが作成されます。
アプリにサイコロの画像を追加する
- Android Studio で、[View] > [Tool Windows] > [Resource Manager] をクリックします。
- [+] > [Import Drawables] をクリックしてファイル ブラウザを開きます。

- 6 つのサイコロの画像フォルダを見つけて選択し、アップロードに進みます。
アップロードされた画像は次のようになります。

- [次へ] をクリックします。

[Import drawables] ダイアログが表示され、ファイル構造内のリソース ファイルの移動先が表示されます。
- [Import] をクリックして、6 つの画像をインポートすることを確認します。
画像が [Resource Manager] ペインに表示されます。

お疲れさまでした。次のタスクでは、これらの画像をアプリで使用します。
Image コンポーザブルを追加する
サイコロの画像は [Roll] ボタンの上に表示されます。Compose は、その性質上、UI コンポーネントを順番に配置します。言い換えると、最初に宣言されたコンポーザブルが最初に表示されます。つまり、最初に宣言されたコンポーザブルが、その後に宣言されたコンポーザブルの上または前に表示されます。Column コンポーザブル内のコンポーザブルは、デバイス上で上下に重なり合って表示されます。このアプリでは、Column を使用してコンポーザブルを縦に積み重ねます。そのため、Column() 関数内で最初に宣言されたコンポーザブルは、同じ Column() 関数内で後に宣言されたコンポーザブルより前に表示されます。
Image コンポーザブルを追加するには: 
- Column()関数本体で、- Button()関数の前に- Image()関数を作成します。
MainActivity.kt
Column(
    modifier = modifier,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Image()
    Button(onClick = { /*TODO*/ }) {
      Text(stringResource(R.string.roll))
    }
}
- Image()関数に- painter引数を渡し、ドローアブル リソース ID 引数を受け入れる- painterResource値を代入します。ここでは、リソース ID- R.drawable.dice_1引数を渡します。
MainActivity.kt
Image(
    painter = painterResource(R.drawable.dice_1)
)
- アプリで画像を作成するときは、必ず「コンテンツの説明」を用意してください。コンテンツの説明は、Android 開発における重要な要素です。ユーザー補助を強化するために、それぞれの UI コンポーネントに説明を付けます。コンテンツの説明について詳しくは、各 UI 要素について説明するをご覧ください。コンテンツの説明をパラメータとして画像に渡すことができます。
MainActivity.kt
Image(
    painter = painterResource(R.drawable.dice_1),
    contentDescription = "1"
)
これで、必要な UI コンポーネントがすべて揃いました。しかし、Button と Image が少し詰まっています。

- これを修正するには、ImageコンポーザブルとButtonコンポーザブルの間にSpacerコンポーザブルを追加します。Spacerは、パラメータとしてModifierを受け取ります。この場合、ImageがButtonの上にあるため、両者の間に縦方向のスペースが必要です。そのため、Modifierの高さを設定してSpacerに適用できます。高さを16.dpに設定してみます。通常、dp ディメンションは4.dp単位で変更されます。
MainActivity.kt
Spacer(modifier = Modifier.height(16.dp))
- [Preview] ペインで、[Build & Refresh] をクリックします。
次の画像のように表示されます。

7. サイコロを振るロジックを作成する
必要なコンポーザブルがすべて揃ったため、ボタンをタップするとサイコロが振られるようにアプリを変更します。
ボタンをインタラクティブにする
- DiceWithButtonAndImage()関数で、- Column()関数の前に- result変数を作成し、- 1値と等しくなるように設定します。
- Buttonコンポーザブルを見てみると、内部にコメント- /*TODO*/を含む中かっこのペアに設定された- onClickパラメータが渡されています。ここでは、この中かっこはラムダというものを表します。中かっこの内部がラムダ本体です。関数を引数として渡す場合については、「コールバック」とも呼ばれます。
MainActivity.kt
Button(onClick = { /*TODO*/ })
ラムダは関数リテラルです。他の関数と同様に機能しますが、fun キーワードで個別に宣言するのではなく、インラインで記述し、式として渡します。Button コンポーザブルは、関数が onClick パラメータとして渡されることを想定しています。ここはラムダを使用するために最適です。このセクションではラムダ本体を記述します。
- Button()関数で、- onClickパラメータのラムダ本体の値からコメント- /*TODO*/を削除します。
- サイコロの出目はランダムです。それをコードに反映させるには、正しい構文を使用して乱数を生成する必要があります。Kotlin では、数値範囲に対して random()メソッドを使用できます。onClickラムダ本体で、result変数を 1~6 の範囲に設定し、その範囲に対してrandom()メソッドを呼び出します。Kotlin では、範囲の最初の数字と最後の数字の間にピリオドを 2 つ入れることで範囲を指定することに留意してください。
MainActivity.kt
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    var result = 1
    Column(
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(
            painter = painterResource(R.drawable.dice_1),
            contentDescription = "1"
        )
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { result = (1..6).random() }) {
            Text(stringResource(R.string.roll))
        }
    }
}
これでボタンをタップできるようになりましたが、ボタンをタップしても見た目は変わりません。機能を構築する必要があります。
Dice Roller アプリに条件を追加する
前のセクションでは、result 変数を作成し、1 値にハードコードしました。最終的に、[Roll] ボタンをタップすると result 変数の値がリセットされ、表示される画像が決定されます。
コンポーザブルは、デフォルトではステートレスです。つまり、値は保持されず、システムがいつでも再コンポーズでき、結果的に値がリセットされます。しかし、Compose ではこれを簡単に回避できます。コンポーズ可能な関数は、remember コンポーザブルを使用してオブジェクトをメモリに格納できます。
- result変数を- rememberコンポーザブルにします。
remember コンポーザブルには、渡す関数が必要です。
- rememberコンポーザブル本体で、- mutableStateOf()関数を渡してから、その関数に- 1引数を渡します。
mutableStateOf() 関数はオブザーバブルを返します。オブザーバブルについては後ほど詳しく説明しますが、ここでは基本的に、result 変数の値が変更されると再コンポーズがトリガーされ、結果の値が反映されて、UI が更新されます。
MainActivity.kt
var result by remember { mutableStateOf(1) }
ボタンをタップすると result 変数が乱数の値で更新されます。
result 変数を使用して、表示する画像を決定できるようになりました。
- result変数のインスタンス化の下で、不変の- imageResource変数を作成して- result変数を受け入れる- when式に設定し、考えられる結果をそれぞれのドローアブルに設定します。
MainActivity.kt
val imageResource = when (result) {
    1 -> R.drawable.dice_1
    2 -> R.drawable.dice_2
    3 -> R.drawable.dice_3
    4 -> R.drawable.dice_4
    5 -> R.drawable.dice_5
    else -> R.drawable.dice_6
}
- Imageコンポーザブルの- painterResourceパラメータに渡される ID を- R.drawable.dice_1ドローアブルから- imageResource変数に変更します。
- result変数を- toString()で文字列に変換し、- contentDescriptionとして渡すことで、- result変数の値を反映するように- Imageコンポーザブルの- contentDescriptionパラメータを変更します。
MainActivity.kt
Image(
   painter = painterResource(imageResource),
   contentDescription = result.toString()
)
- アプリを実行します。
Dice Roller アプリが完全に機能するようになりました。

8. 解答コードを取得する
この Codelab の完成したコードをダウンロードするには、次の git コマンドを使用します。
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-dice-roller.git
または、リポジトリを ZIP ファイルとしてダウンロードし、Android Studio で開くこともできます。
解答コードを確認する場合は、GitHub で表示します。
- プロジェクト用に提供されている GitHub リポジトリ ページに移動します。
- ブランチ名が Codelab で指定されたブランチ名と一致していることを確認します。たとえば、次のスクリーンショットでは、ブランチ名は main です。

- プロジェクトの GitHub ページで、[Code] ボタンをクリックすると、ポップアップが表示されます。

- ポップアップで、[Download ZIP] をクリックして、プロジェクトをパソコンに保存します。ダウンロードが完了するまで待ちます。
- パソコンに保存したファイルを見つけます([ダウンロード] フォルダなど)。
- ZIP ファイルをダブルクリックして展開します。プロジェクト ファイルが入った新しいフォルダが作成されます。
Android Studio でプロジェクトを開く
- Android Studio を起動します。
- [Welcome to Android Studio] ウィンドウで、[Open] をクリックします。

注: Android Studio がすでに開いている場合は、メニューから [File] > [Open] を選択します。

- ファイル ブラウザで、展開したプロジェクト フォルダがある場所([ダウンロード] フォルダなど)に移動します。
- そのプロジェクト フォルダをダブルクリックします。
- Android Studio でプロジェクトが開かれるまで待ちます。
- 実行ボタン  をクリックして、アプリをビルドし、実行します。期待どおりにビルドされることを確認します。 をクリックして、アプリをビルドし、実行します。期待どおりにビルドされることを確認します。
9. まとめ
Compose を使用して Android 用のインタラクティブな Dice Roller アプリを作成しました。
概要
- コンポーズ可能な関数を定義する。
- Composition でレイアウトを作成する。
- Buttonコンポーザブルでボタンを作成します。
- drawableリソースをインポートする。
- Imageコンポーザブルを使用して画像を表示する。
- コンポーザブルでインタラクティブな UI を作成する。
- rememberコンポーザブルを使用して、Composition 内のオブジェクトをメモリに保存する。
- mutableStateOf()関数で UI を更新して、オブザーバブルを作成する。
