この記事は Allison Chang, Weifang Sun, Manuel Wang による Android Developers Blog の記事 " New features and tools to help you showcase your Play Store listing " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
アプリのユーザーになる可能性のある人々にアプリの機能や価値を理解してもらうには、 Google Play ストアの掲載情報を効果的に活用することが一番の方法です。皆さんが提供するアセットや情報(説明文、イメージ、動画)は、ユーザーが何をダウンロードすべきかを決めるうえで欠かせません。
タブレット、スマートウォッチ、テレビなど、スマートフォン以外のネットワーク デバイスが使われることが多くなっている今、それぞれのフォーム ファクタに合わせてアプリのアセットを提供することがこれまでになく重要になっています。実際に、モバイル以外のアクティブな Android デバイス数は、この 1 年間で 30% 近く増加しています。
2022 年 10 月 13 日、ストアの掲載情報のアセットをこれまで以上に Google Play の中心に据えるための新機能についてお知らせしました。さらに、アプリの掲載情報を最適化し、有効なインストール数を増やすために役立つベスト プラクティスも紹介します。
現在、タブレット、折りたたみ式、Chromebook などの大画面で、ユーザーがデバイスに最適なアプリを探せるようにするための機能強化を続けています。2022 年の I/O でお知らせした (動画/日本語字幕あり) ように、大画面向けの Google Play ストアを再設計し、アプリとゲームのホームに直接スクリーンショット、動画、説明文を表示するようにします。
Google Play ストアの大画面向けホームページ(2023 年)
コンテンツを前面に出すアプローチの目的は、ストアでアプリについて詳しく説明して、それをインストールするかどうかの判断に役立ててもらうことです。
また、大画面向けにアプリを表示するベスト プラクティスとして、コンテンツの品質に関するガイドラインを公開しました。2023 年の早いうちに、この基準に従ってアセットを提供したアプリは、見栄えの良いフォーマットで Google Play に表示されるようになります。これはアプリの宣伝性には影響しません。 Google Play ストアでのアプリの表示方法が変わるだけです。
現在、Chromebook で Google Play ストアを表示すると、ストアの掲載情報ページにはタブレット版かスマートフォン版のアプリのスクリーンショットが表示されます。これは必ずしも Chromebook のエクスペリエンスを正確に表しているとは限らないので、Play Console で Chromebook のスクリーンショットをアップロードできる機能をリリースしています。
Play Developer Console での Chromebook のスクリーンショット
スクリーンショットは 8 枚まで登録でき、主に Chromebook の Google Play ストアに表示されます。このスクリーンショットは、アプリの掲載情報ページと Google Play ホームページの両方に表示されます。
スクリーンショットは、16:9、横向き、解像度 1080-7690px のものを推奨します。
使ってみたい方は、Play Console の [メインのストアの掲載情報] セクションをご覧ください。
新たに ChromeOS のスクリーンショットがサポートされたため、タブレット向けの品質に関するガイドラインも更新し、大画面での整合性が確保されるようにしました。すでにアップロードされているタブレットのスクリーンショットは影響を受けませんが、アプリをアップデートする際に新しいスクリーンショットを簡単に生成できるようになります。
先月には、フォーム ファクタ固有のホームページを導入しました。これは、モバイル以外のデバイスも持っているユーザーがスマートフォンを開いたときに表示される専用の画面です。このホームページでは、スマートウォッチ、テレビ、自動車に最適なタイトルをスマートフォンから表示できるので、皆さんのアプリや詳しいストアの掲載情報の視認性が向上します。
その他のデバイス向けのホームページ
さらに、Google Play で新しいデバイス フィルタを使って検索結果を絞り込めるようになっています。このフィルタを有効にすると、選択したデバイスと互換性のあるタイトルのみが検索結果に表示されます。
以上の変更により、詳しいストアの掲載情報が Google Play ではるかに目立ちやすくなります。そこで、アプリのアセットの最適化に役立ついくつかの方法を紹介します。
アプリやゲームの中核となる画面をデバイス固有のスクリーンショットで示す
Play Console には、アプリやゲームがさまざまな種類のデバイスでどのように見えるかを示すスクリーンショットをアップロードできます。また、フォーム ファクタ独自の機能を紹介することもできます。スクリーンショットを選ぶときは、アプリの主要なユーザーフローを表す画像を使うようにしてください。そうすることで、あらゆるデバイスのユーザーが、実際のアプリやゲームがどのようなものであるかを把握できるようになります。
デバイスの画像は慎重に利用する
ストアの掲載情報に実機を掲載すると、スクリーンショットや動画がすぐに古くなったり、一部のユーザーが身近に感じられなかったりする可能性があります。時間をかけずにアセットを管理できるように、アプリやゲームのスクリーンショットや動画のみを使うようにしましょう。
高品質イメージを適切なアスペクト比と解像度で利用する
あらゆるサイズの画面で見栄えの良いスクリーンショットにするためには、高品質イメージを使うことが欠かせません。ピクセルが見えるようなスクリーンショット、引き延ばされたり圧縮されたりしているスクリーンショット、向きが正しくないスクリーンショットは使わないようにしましょう。
アセットにテキストがかぶらないようにする
スクリーンショットや動画が Google Play のホームページできれいに表示されるように、テキストを使いすぎないようにしてください。アセットは、画面サイズに収まるように拡大縮小される可能性があります。過度なテキストを避けることで、意図せずにテキストが切れることがなくなります。
テキストを使う必要がある場合は、有効期限がある頻繁に更新しなければならないテキストは避けるようにしましょう。
Google Play で皆さんのストアの掲載情報を際立たせるための方法は、現在もテストが続いていますが、アセットの品質の重要性が変わることはありません。あらゆる種類のデバイスに最高の形でアプリを見せることができるように、ここで紹介した機能やヒントを活用していただけることを願っています。着手するうえで、さらに詳しいヒントを確認したい方は、コンテンツの品質に関するガイドラインをご覧ください。
この記事は、 Google Developer Expert (GDE) @STAR_ZERO さんに寄稿いただいたゲスト記事です。この「エキスパートに学ぶシリーズ」では、まだ Jetpack Compose を使ったことがないデベロッパー向けに既存アプリで段階的に導入を進める方法や導入するメリットについて GDE の方々よりご紹介いただきます。
新規アプリにて Jetpack Compose を導入することは最適な方法ですが、 Jetpack Compose は View-base UI と統合することができ、既存アプリに対しても段階的に導入が可能になっています。
この記事では、まだ Jetpack Compose を使用したことがない方向けに、既存アプリに Jetpack Compose を導入する手順について紹介します。
Jetpack Compose を最大限に活かすために、最新の Android Studio 安定版をインストールします。また、それにあわせて build.gradle の Android Gradle Plugin のバージョンもアップデートします。今回は執筆時点での安定版である 7.3.0 を指定しています。
buildscript { // ... dependencies { classpath "com.android.tools.build:gradle:7.3.0" // ... }}
buildscript {
// ...
dependencies {
classpath "com.android.tools.build:gradle:7.3.0"
}
Jetpack Compose は Kotlin でのみ実装することが可能になっています。まだ Kotlin を使用していないプロジェクトについては Kotlin の導入を行う必要があります。 Kotlin は Java と同時に使用することができます。既存アプリに Kotlin を導入するにはこちらのページを確認してください。
Jetpack Compose を使用するには Gradle で構成を追加する必要があります。
サポートされてる minSdk のバージョンは 21 からです。21 以上にアップデートし、さらに Jetpack Compose を有効にする設定と、 Java と Kotlin のターゲットバージョンの設定を追加します。
最後に使用してる Kotlin バージョンによって適切な Jetpack Compose Compiler のバージョンが変わります。こちらのページの Kotlin と Compiler のバージョンの対応表があるので、適切なバージョンを指定します。
android { defaultConfig { // … // Jetpack Compose がサポートされているのは 21 から minSdk 21 } // … buildFeatures { // Jetpack Compose を有効にする compose true } // Java と Kotlin のターゲットを Java8 に設定 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } composeOptions { // Jetpack Compose の Compiler バージョンを指定 // Kotlin のバージョンから適切なバージョンを指定する kotlinCompilerExtensionVersion "1.3.1" }}
android {
defaultConfig {
// …
// Jetpack Compose がサポートされているのは 21 から
minSdk 21
buildFeatures {
// Jetpack Compose を有効にする
compose true
// Java と Kotlin のターゲットを Java8 に設定
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
kotlinOptions {
jvmTarget = '1.8'
composeOptions {
// Jetpack Compose の Compiler バージョンを指定
// Kotlin のバージョンから適切なバージョンを指定する
kotlinCompilerExtensionVersion "1.3.1"
以下のように build.gradle に必要なライブラリを追加します。
dependencies { // … // Activity と Jetpack Compose の統合 implementation 'androidx.activity:activity-compose:1.6.0' // Material Design implementation 'androidx.compose.material:material:1.2.1' // Jetpack Compos eのアニメーション implementation 'androidx.compose.animation:animation:1.2.1' // プレビュー等のツール implementation 'androidx.compose.ui:ui-tooling:1.2.1' // ViewModelとJetpack Composeの統合 implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1'}
// Activity と Jetpack Compose の統合
implementation 'androidx.activity:activity-compose:1.6.0'
// Material Design
implementation 'androidx.compose.material:material:1.2.1'
// Jetpack Compos eのアニメーション
implementation 'androidx.compose.animation:animation:1.2.1'
// プレビュー等のツール
implementation 'androidx.compose.ui:ui-tooling:1.2.1'
// ViewModelとJetpack Composeの統合
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1'
このとき、推移的依存関係により使用してるライブラリのバージョンも上がってしまう可能性があるので注意です。
Jetpack Compose で使用するテーマは新しく作成していくこともできますし、既存 View システムで使用しているテーマを再利用することもできます。
Material Component を使用している場合は MDC Compose Theme Adapter 、 AppCompat のテーマを使っている場合は AppCompat Compose Theme Adapter を使用することで再利用できます。
// MDC Compose Theme Adapterimplementation "com.google.android.material:compose-theme-adapter:1.1.19"// AppCompat Compose Theme Adapterimplementation "com.google.accompanist:accompanist-appcompat-theme:0.25.1"
// MDC Compose Theme Adapter
implementation "com.google.android.material:compose-theme-adapter:1.1.19"
// AppCompat Compose Theme Adapter
implementation "com.google.accompanist:accompanist-appcompat-theme:0.25.1"
MDC Compose Theme Adapter を使用する場合は MdcTheme を使用することで XML で定義されてるTheme を再利用することが出来ます。
MdcTheme { Box( modifier = Modifier.background( // XML の Theme の colorPrimary の色 color = MaterialTheme.colors.primary ) ) { Text( text = "Hello, Compose!", // XML の Theme の textAppearanceHeadline1 のスタイル style = MaterialTheme.typography.h1 ) }}
MdcTheme {
Box(
modifier = Modifier.background(
// XML の Theme の colorPrimary の色
color = MaterialTheme.colors.primary
)
) {
Text(
text = "Hello, Compose!",
// XML の Theme の textAppearanceHeadline1 のスタイル
style = MaterialTheme.typography.h1
新規画面を Activity と Jetpack Compose で実装する例になります。 ComponentActivity を継承した Activity を作成し、 setContent のラムダブロック内に Jetpack Compose の実装をしていきます。
class MyActivity: ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { // この中に Jetpack Compose のコードを書いていく MdcTheme { Text(text = "Hello, Compose!") } } }}
class MyActivity: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// この中に Jetpack Compose のコードを書いていく
Text(text = "Hello, Compose!")
新規画面を Fragment と Jetpack Compose で実装する場合は次のようになります。 Fragment で実装する際は注意があり、 setViewCompositionStrategy を設定して Jetpack Compose の Composition を正しく破棄する必要があります。詳しくはこちらのページを確認してください。
class MyFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { return ComposeView(requireContext()).apply { // Fragment の View が破棄されるときに Composition も破棄する setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { // この中に Jetpack Compose のコードを書いていく MdcTheme { Text(text = "Hello, Compose!") } } } }}
class MyFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
// Fragment の View が破棄されるときに Composition も破棄する
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
紹介したとおり、既存アプリにも Jetpack Compose を導入することは意外と簡単に出来るようになっています。もしかしたら既存アプリに導入するのは難しいと思っている方もいらっしゃるかもしれませんが、段階的に導入することで可能なところから少しずつ Jetpack Compose を使っていくことができます。
Jetpack Compose を使うことで効率よく UI を実装することができますので、ぜひ挑戦してみてください。
この記事は Chris Arriola による Android Developers - Medium の記事 " Composable Functions " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
前回の MAD Skills の Compose の基本に関する記事では、UI を Kotlin で関数として記述するという Compose の考え方について説明しました。もう XML は必要ありません。今回の記事では、この関数をさらに掘り下げ、それを使ってどのように UI を作るのか説明します。
リマインダーですが、10 月 13 日のライブ Q&A セッションで、Compose の基本についての質問にお答えします。質問はこの記事または YouTube にコメントを残すか、#MADCompose ハッシュタグを使って Twitter に投稿してください。( ※ライブ Q&A セッションは終了しました。録画はこちらからご確認いただけます。日本語字幕は、YouTube の自動字幕機能から日本語を選択してください。 )
この関数の動作の仕組みを理解するため、選択式の質問が 1 つある画面の作り方について考えてみます。これは、Compose サンプルに含まれている Jetsurvey の画面です。
この記事の動画版 (英語) はこちらからご覧いただけます。
* 日本語字幕は、YouTube の自動字幕機能から日本語を選択してください
Compose では、このアンケートの回答の 1 つの選択肢は、Image、Text、RadioButton を含む Row 関数として記述できます。
// Copyright 2022 Google LLC. // SPDX-License-Identifier: Apache-2.0@Composablefun SurveyAnswer(answer: Answer) { Row { Image(answer.image) Text(answer.text) RadioButton(false, onClick = { /* … */ }) }}
// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0
@Composable
fun SurveyAnswer(answer: Answer) {
Row {
Image(answer.image)
Text(answer.text)
RadioButton(false, onClick = { /* … */ })
Compose で UI コンポーネントを作る場合、関数に @Composable アノテーションをつけます。このアノテーションは、対象の関数がデータを UI に変換する(つまり、選択肢を UI にする)ものであることを Compose コンパイラに伝えます。¹
このアノテーションをつけた関数は、コンポーズ可能な関数、またはコンポーザブルと呼ばれます。Compose では、この関数が UI の構成要素になります。このアノテーションは簡単にすばやく追加できるので、UI を再利用可能な要素のライブラリとして整理しやすくなります。
たとえば、回答候補として提示する一連の選択肢を実装するために、選択肢のリストを受け取る SingleChoiceQuestion という新しい関数を定義し、そこで先ほど定義した SurveyAnswer 関数を呼び出すことができます。
SingleChoiceQuestion
SurveyAnswer
// Copyright 2022 Google LLC. // SPDX-License-Identifier: Apache-2.0 @Composablefun SingleChoiceQuestion(answers: List<Answer>) { Column { answers.forEach { answer -> SurveyAnswer(answer = answer) } }}
fun SingleChoiceQuestion(answers: List<Answer>) {
Column {
answers.forEach { answer ->
SurveyAnswer(answer = answer)
SingleChoiceQuestion はパラメータを受け取れるので、それを使ってアプリのロジックを指定できます。この場合は、回答候補のリストを受け取って、そこに含まれる選択肢を UI に表示します。このコンポーザブルは何も返さずに(つまり、`Unit` を返します)UI を生成している点に注意してください。具体的に言えば、生成されるのは Column レイアウト コンポーザブルです。これは Compose ツールキットの一部で、項目を垂直に並べます。この Column の中に、それぞれの選択肢を表す SurveyAnswer コンポーザブルが生成されます。
Column
コンポーザブルは不変です。また、いずれかの選択肢への参照を保持するというようなこと、つまり、コンポーザブルへの参照を保持して、後からその内容を更新することはできません。必要なすべての情報は、コンポーザブルを呼び出すときにパラメータとして渡さなければなりません。
関数は Kotlin で書かれるので、Kotlin の構文と制御フローをフル活用して UI を生成できます。ここでは、forEach で各選択肢の反復処理をし、SurveyAnswer を呼び出して表示しています。条件に基づいて何かを表示したいなら、if 文を使うだけで簡単に実現できます。View.visibility = View.GONE や View.INVISIBLE はもう必要ありません。Compose のような宣言的 UI フレームワークでは、与えられる入力によって UI の表示を変えたい場合、それぞれの入力値に対して UI をどのように表示するかをコンポーザブルに記述しなければなりません。これを実現するには、次のスニペットのような条件文を使います。
forEach
View.visibility = View.GONE
View.INVISIBLE
// Copyright 2022 Google LLC. // SPDX-License-Identifier: Apache-2.0 @Composablefun SingleChoiceQuestion(answers: List<Answer>) { Column { if (answers.isEmpty()) { Text("There are no answers to choose from!") } else { answers.forEach { answer -> SurveyAnswer(answer = answer) } } }}
if (answers.isEmpty()) {
Text("There are no answers to choose from!")
} else {
コンポーザブルは、高速かつ副作用がないものでなくてはなりません。同じ引数で複数回呼ばれた場合、同じ動作になる必要があり、プロパティやグローバル変数を変更してはいけません。この特性を持つ関数を、 「べき等」と呼びます。新しい値で関数を再呼び出しするときに、UI が正しく生成されるために、この特性はすべてのコンポーザブルに必須となります
関数に渡すパラメータで UI のすべてが決まる点に注意してください。状態を UI に変換するというのは、このことを表しています。UI が常に同期されることは、関数のロジックによって保証されます。選択肢のリストが変更されれば、関数が再実行されて新しい選択肢のリストから新しい UI が生成され、必要に応じて UI が再描画されます。
状態が変化したときに UI を再生成するこの処理を、再コンポーズと呼びます。コンポーザブルは不変なので、再コンポーズは新しい状態で UI を更新する唯一の仕組みです。
再コンポーズは、コンポーザブルが別の関数パラメータで再度呼び出されたときに発生します。再コンポーズが発生するのは、関数が依存する状態が変わるからです。
たとえば、SurveyAnswer コンポーザブルが isSelected パラメータを受け取るとしましょう。このパラメータは、選択肢が選択されているかどうかを表します。最初は、どの選択肢も選択されていません。
isSelected
// Copyright 2022 Google LLC. // SPDX-License-Identifier: Apache-2.0 @Composablefun SingleChoiceQuestion(answers: List<Answer>) { answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = false, ) }}
SurveyAnswer(
answer = answer,
isSelected = false,
ビューの世界では、ビューがそれぞれの状態を保持しているので、いずれかの選択肢の UI 要素をタップすると、その表示が切り替わります。しかし、Compose の世界では、すべての SurveyAnswer コンポーザブルで false が指定されているので、ユーザーが操作したとしても、すべての選択肢が未選択のままになります。ユーザーの操作に視覚的に応答できるようにするには、コンポーザブルを再コンポーズして新しい状態で UI を再生成できるようにしなければなりません。
Compose
そのために、選択されている選択肢を保持する新しい変数を追加します。さらに、この変数は MutableState (英語) でなければなりません。この型は、Compose ランタイムに組み込まれている監視可能な型です。状態が変化すると、それを読み取るすべてのコンポーザブルが自動的に再コンポーズされるようにスケジュールされます。新しい MutableState は、mutableStateOf (英語) メソッドを使って次のように作成します。
MutableState
// Copyright 2022 Google LLC. // SPDX-License-Identifier: Apache-2.0 @Composablefun SingleChoiceQuestion(answers: List<Answer>) { // Initialize selectedAnswer to null since no answer will be selected at first var selectedAnswer: MutableState<Answer?> = mutableStateOf(null) answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = (selectedAnswer.value == answer), ) }}
// Initialize selectedAnswer to null since no answer will be selected at first
var selectedAnswer: MutableState<Answer?> =
mutableStateOf(null)
isSelected = (selectedAnswer.value == answer),
上のスニペットでは、現在選択されている選択肢である selectedAnswer と比較することで、isSelected の値を更新しています。selectedAnswer は MutableState 型なので、選択されている選択肢は value プロパティを使って取得します。この値が変化すると、Compose は自動的に SurveyAnswer を再実行し、それによって選択されている選択がハイライト表示されます。
selectedAnswer
ただし、上のスニペットは正しく動作しません。selectedAnswer 値は、再コンポーズが発生しても保持されなければなりません。そうでないと、SingleChoiceQuestion が再実行されたときにこの値が上書きされてしまいます。これを解決するため、忘れないように mutableStateOf の中で呼び起こす必要があります。これにより、コンポーザブルが再コンポーズされても、値がリセットされずに保持されることが保証されます。構成の変更が発生した場合でも値を保持する別の方法として、rememberSaveable を使うこともできます。
mutableStateOf
rememberSaveable
// Copyright 2022 Google LLC. // SPDX-License-Identifier: Apache-2.0 @Composablefun SingleChoiceQuestion(answers: List<Answer>) { var selectedAnswer: MutableState<Answer?> = rememberSaveable { mutableStateOf(null) } answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = (selectedAnswer.value == answer), ) }}
rememberSaveable { mutableStateOf(null) }
上のコード スニペットは、さらに selectedAnswer 変数で Kotlin の委譲プロパティ構文を使うと、改善できます。こうすることで、型を MutableState<Answer?> から Answer? 値に変えることができます。この構文は、ベースとなる状態の値を直接扱うことができ、MutableState オブジェクトの value プロパティを呼び出す必要がなくなるかなり優れたものです。
MutableState<Answer?>
この新しく追加した状態があれば、onAnswerSelected パラメータにラムダ関数を渡すことで、ユーザーが選択したときにアクションを実行できるようになります。このラムダの定義で、selectedAnswer の値を新しいものに設定します。
onAnswerSelected
// Copyright 2022 Google LLC. // SPDX-License-Identifier: Apache-2.0 @Composablefun SingleChoiceQuestion(answers: List<Answer>) { var selectedAnswer: Answer? by rememberSaveable { mutableStateOf(null) } answers.forEach { answer -> SurveyAnswer( answer = answer, isSelected = (selectedAnswer == answer), onAnswerSelected = { answer -> selectedAnswer = answer } ) }}
var selectedAnswer: Answer? by
isSelected = (selectedAnswer == answer),
onAnswerSelected = { answer -> selectedAnswer = answer }
前回の記事を覚えているでしょうか?イベントは状態を更新する仕組みでした。この例では、ユーザーが選択肢をタップしたときに、onAnswerSelected イベントが実行されます。
Compose のランタイムは、状態が読み取られた場所を自動追跡しているので、その状態に依存するコンポーザブルを効率的に再コンポーズできます。そのため、状態を明示的に観測したり、手動で UI を更新したりする必要はなくなります。
コンポーズ可能な関数には、意識しておくべき動作特性がほかにもあります。この動作の特性上、コンポーズ可能な関数は、副作用をもたらさないことに加え、同じ引数で何度呼び出しても同じ動作になることが重要です。
1. コンポーズ可能な関数は任意の順序で実行できる
次のスニペットをご覧ください。このコードは順次実行されると思うかもしれません。しかし、必ずしもそうとは限りません。Compose は、一部の UI 要素の優先度が高いことを認識しているので、そのような要素は最初に描画される可能性があります。たとえば、タブレイアウトに 3 つの画面を描画するコードがあるとしましょう。StartScreen が最初に実行されると思うかもしれませんが、これらの関数はどのような順序でも実行することができます。
StartScreen
// Copyright 2022 Google LLC. // SPDX-License-Identifier: Apache-2.0 @Composablefun ButtonRow() { MyFancyNavigation { StartScreen() MiddleScreen() EndScreen() }}
fun ButtonRow() {
MyFancyNavigation {
StartScreen()
MiddleScreen()
EndScreen()
2. コンポーズ可能な関数は並列して実行できる
コンポーザブルは並列に実行できるので、複数のコアを活用することで画面のレンダリング パフォーマンスが向上します。次のコード スニペットでは、コードは副作用なく実行され、入力リストが UI に変換されます。
ただし、下のスニペットのように、関数からローカル変数に書き込みをする場合、コードは副作用なしとは見なされません。下のコードと同じようなことをすると、UI の動作が不適切になる可能性があります。
// Copyright 2022 Google LLC. // SPDX-License-Identifier: Apache-2.0 @Composablefun ListComposable(myList: List<String>) { Row(horizontalArrangement = Arrangement.SpaceBetween) { Column { for (item in myList) { Text("Item: $item") } } Text("Count: ${myList.size}") }}
fun ListComposable(myList: List<String>) {
Row(horizontalArrangement = Arrangement.SpaceBetween) {
for (item in myList) {
Text("Item: $item")
Text("Count: ${myList.size}")
3. 再コンポーズは可能な限りスキップする
Compose は、可能な限り、更新する必要がある UI のみを再コンポーズしようとします。再コンポーズをトリガーした状態を使わないコンポーザブルでは、再コンポーズがスキップされます。次のスニペットで name 文字列が変更された場合、Header コンポーザブルと Footer コンポーザブルはこの状態に依存していないため、再実行されません。
Header
Footer
// Copyright 2022 Google LLC. // SPDX-License-Identifier: Apache-2.0 @Composablefun GreetingScreen(name: String) { Column { Header() Greeting(name = name) Footer() }}
fun GreetingScreen(name: String) {
Header()
Greeting(name = name)
Footer()
4. 再コンポーズは厳密なものではない
再コンポーズは厳密なものではありません。つまり、再コンポーズはパラメータが再度変化する前に終わると想定されます。再コンポーズが終わる前にパラメータが変化した場合、Compose は再コンポーズをキャンセルし、新しいパラメータでもう一度再コンポーズを始める可能性があります。
5. コンポーズ可能な関数は何度も実行されることがある
最後に挙げるのは、コンポーズ可能な関数は何度も実行される可能性があることです。これが問題になるのは、コンポーズ可能な関数に毎フレーム実行する必要があるアニメーションが含まれる場合です。フレームの欠落が起きないようにするために、コンポーズ可能な関数を高速にすることが重要なのはそのためです。
長時間実行オペレーションが必要な場合は、コンポーズ可能な関数で行わないようにしてください。このようなオペレーションは、UI スレッドの外で実行し、コンポーザブルではその結果だけを渡すようにします。
また、コンポーザブルのいくつかの興味深い特性についても学びました。コンポーザブルには次の特性があります。
Compose ツールキットでは、基礎となる強力なコンポーザブルがたくさん提供されています。こういったコンポーザブルは、美しいアプリを作るうえで役立ちます。この点については、次回説明したいと思います。それまで待てない方は、以下のリソースを確認してみてください。
質問がある方は、下にコメントを記入するか、Twitter でハッシュタグ #MADCompose をお使いください。10 月 13 日に予定されている本シリーズのライブ Q&A で質問にお答えします。お楽しみに!( ※ライブ Q&A セッションは終了しました。録画はこちらからご確認いただけます。日本語字幕は、YouTube の自動字幕機能から日本語を選択してください。 )
¹ @Composable アノテーションがついた関数がすべて UI を返すわけではありません。たとえば、remember を呼び出す関数は UI を返しませんが、Compose UI ツリーのノードを生成します。
この記事は Yasmine Evjen による Android Developers Blog の記事 " Android Dev Summit ‘22: Coming to you, online and around the world! " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
Android Dev Summit (英語) が戻ってきます。そして2022 年は、皆さんがお住まいの場所で開催します!オンラインで視聴する方であっても、2019 年以来初めて世界各地で直接集まる方であっても、皆さんとお会いできるのが待ち遠しくてたまりません。デバイスに依存しない優れたアプリの構築について、ソースから学べる絶好の機会です。
Android Dev Summit ‘22 は、10 月 24 日の基調講演から始まります。基調講演では、Android チームから直接話を聞くことができます。最先端の Android 開発についての最新情報、私たちのコア プラットフォームのイノベーション、ウェアラブルや大画面などのさまざまなデバイスで Android の勢いを活用する方法についてお話しします。このテクニカル基調講演では、たくさんのデモも実演する予定です。10 月 24 日太平洋時間午前 9 時から始まり、YouTube でライブ配信します。
ADS で特に重要なのが、詳細なテクニカル セッションであり、私たちもとても楽しみにしています。2022 年は、3 週間にわたって 3 つのトラックのセッションを YouTube (動画/英語) でライブ配信します。
ADS は、皆さんと直接つながることができる機会です。皆さんが最も重視することや、Android での開発を楽にするために私たちができることについて、お聞きしたいと考えています。そのためには、直接お会いする以上の方法はありません。多くの皆さんにとって、長距離の移動はまだ難しいので、できる限り皆さんのもとに伺うようにしたいと考えました。そこで2022 年は、世界各地でイベントを開催します。ADS の最初の開催地は、10 月 24 日のサンフランシスコ ベイエリアです(近くにお住まいの方は、こちら (英語) から参加を応募できます)。その次の ADS ’22 は、11 月 9 日のロンドンです(ロンドンにお住まいの方は、こちら (英語) から参加を応募できます)。12 月には、アジアの何箇所かでロードショーをする予定です( ※日本での開催に関する詳細は後日共有予定です。 )
直接来ることはできない方にも、ぜひオンラインで参加いただきたいと考えています。各セッション トラックの最後には、ライブ Q&A として #AskAndroid を開催し、皆さんからの質問にお答えします。質問は、Twitter か YouTube ライブストリームのコメントに #AskAndroid を付けて投稿してください。ライブでお答えできるかもしれませんので、お楽しみに。
今後数週間にわたって、ADS ’22 の情報をウェブサイト (英語) に掲載します。全セッション トラックの詳細などを公開しますので、ぜひご覧ください。Android Developer ニュースレターに登録 (英語) すると、最新情報を受け取ることができます。
皆さんにお会いできることが楽しみです!
** 皆さん、こんにちは。Yasmine Evjen です。Android デベロッパー リレーションズで新しくコミュニティ リードを務めることになりました。#AndroidDevSummit で、皆さんと直接またはバーチャルでお会いできるのが楽しみです。私が大好きな 2 つのこと、つまりエクサイティングな新技術とそれに命を吹き込むデベロッパーの皆さんが 1 つになります。
この記事は Chris Arriola による Android Developers - Medium の記事 " Thinking in Compose " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
Compose 基本シリーズのこの投稿では、Compose の考え方に迫りたいと思います。Jetpack Compose は宣言的 UI フレームワークです。つまり、デベロッパーは UI を表示する手順ではなく、表示する内容を記述します。
質問がある方は、この投稿か YouTube、または Twitter で #MADCompose を使ってコメントを残してください。10 月 13 日のライブ Q & A セッションで皆さんの質問にお答えします。
この記事の内容は、MAD Skills の動画でもご覧いただけます。
ビューを使う場合、現在の画面から望む画面にするために、UI を変更する手順を 1 つ 1 つ記述します。
たとえば、クリックしたボタンの背景色を変えたいとしましょう。ビューを使う場合、まず XML から初期状態の UI を読み込みます。その後、ユーザーがボタンをクリックしたときに、ボタンを探して setColor を呼び出します。望む画面状態になるまで、すべてのユーザーのインタラクションやプロパティに対してこれを繰り返します。
Compose を使う場合、望む状態になるまで個々の UI コンポーネントを変更する(詳しくは後ほど触れますが、これはエラーにつながりやすい操作です)必要はありません。表示する内容をあらかじめ宣言しておけば、画面の更新が必要になったタイミングでそのすべての内容が再宣言されます。
うれしいことに、XML で UI を記述する必要もなくなり、Kotlin の構造をフル活用しながら Kotlin だけで UI を記述できます。
手順ではなく内容を記述して UI を構築できる点が、Compose とビューの主な違いです。Compose の方がはるかに直感的に使えるのはそのためです。この記事では、この 2 つの違いに加えて、アプリ作成の考え方を Compose にどのようにシフトすべきか説明します。
Compose によるアプリ作成の考え方を理解するため、まずはビューでどのように画面を作るかについて確認しましょう。
次に示すのは、Github で公開されている Compose サンプルアプリの 1 つ、Jetsurvey の画面です。この画面には、ユーザーに回答を求める 1 択の質問が表示されています。いずれかを選択すると、次の質問に進むことができます。
説明を簡単にするため、この画面の中の 1 つのコンポーネント、すなわちアンケートの選択肢の 1 つを作る場合について考えます。
選択肢は、1 つのイメージ、何文字かのテキスト、ラジオボタンを水平に並べたものでできています。ビューでは、次のようにして各要素を XML で定義します。
<!-- survey_answer.xml --><LinearLayout android:orientation="horizontal" > <ImageView android:id="@+id/answer_image" ... /> <TextView android:id="@+id/answer_text" ... /> <RadioButton android:id="@+id/answer_radio_button" ... /></LinearLayout>
<!-- survey_answer.xml -->
<LinearLayout android:orientation="horizontal" >
<ImageView android:id="@+id/answer_image" ... />
<TextView android:id="@+id/answer_text" ... />
<RadioButton android:id="@+id/answer_radio_button" ... />
</LinearLayout>
このビューに UI の状態を設定するには、フラグメントかアクティビティで Kotlin か Java の findViewById を使い、それぞれのビューへの参照を取得します。参照を取得できたら、setImage や setText などのセッター関数を呼び出してそれぞれのビューを変更し、望む UI の状態を表示します¹。この手順では、それぞれのビューを個別に変更することで、初期状態の UI から望む UI の状態に更新します。つまり、状態を変更する手順を記述しています。 UI を表示する手順を記述すると書いたのはそのためです。
ユーザーが回答を選択すると(状態変化が発生すると)、選択されたことをビューで視覚的に表現しなければなりません。この例では、ユーザーが質問の回答を選択したときに、[Next] ボタンを有効にする処理も必要です。あるビューが選択されたときの副作用として別のビューを更新したい場合は、ビューにリスナーを設定して影響を受けるビューを明示的に変更します。
ただし、状態が変化したときに手動でビューを更新するという方法は、エラーにつながりやすくなります。ビューを状態に合わせて更新するのを忘れたり、複数の更新が予期しない形で競合したときに正しくない状態になったりする可能性があります。
たとえば、アプリで画面の回転などの構成変更が発生した場合、ユーザーが選択した内容は正しく覚えていたとしても、その過程で [Next] ボタンを再有効化することを忘れてしまうかもしれません。
アプリの生存期間全体にわたって状態の変化に同期することは、ビューを扱う場合に繰り返し遭遇する問題です。この問題の複雑さは、アプリでビューや依存する状態の数が増えるとともに増大していきます。これは解決できない問題ではありませんが、バグの原因になりがちです。
では、同じコンポーネントを Compose で作る場合、どのように考えるかみてみましょう。
Compose でも、先ほどと同じような形で UI を構築できます。つまり、イメージ、テキスト、ラジオボタンを水平に並べたコンテナを配置します。
ただし、XML を書く代わりに、要素を直接 Kotlin で定義します。
// SurveyAnswer.kt@Composablefun SurveyAnswer(answer: Answer) { Row { Image(answer.image) Text(answer.text) RadioButton(false, onClick = { /* … */ }) }}
// SurveyAnswer.kt@Composable
Compose の UI はすでに Kotlin で宣言されているので、XML と Kotlin を行き来する必要はなくなります。
Compose の UI 要素は関数であり、オブジェクトではありません。つまり、UI 要素は参照できないので、後からメソッドを呼び出して変更するようなことはできません。
その代わり、UI 要素のすべての制御は渡す状態(引数)によって行います。ここでは、単に UI に表示する内容を記述します。findViewById、setImage、setText を呼び出す必要はありません。UI は呼び出す関数の中に簡潔に記述します。
望みどおりの状態で UI を表示するため、回答のプロパティを Image 関数と Text 関数に、そして今のところ、未選択を示す false を RadioButton 関数に渡します。
ここでとても重要なビューとの違いがあります。それは、この回答をタップしても項目が選択されないことです。その理由は、RadioButton に常に false を渡しているからです。これは、ユーザーの操作に関係なく未選択のままにするという意味になります(まもなく修正しますので、心配は無用です)。
ビューとは違い、Compose の RadioButton はユーザーのイベントによって自動的に変化する状態を保持しません。RadioButton の状態は渡す値によって決まります。
先ほど、「手順ではなく内容」と述べたのはそのためです。UI 関数に必要な状態を渡すことで、UI として表示する内容を宣言します。UI の要素が変化した場合、新しい状態を渡して、すべてを再度呼び出します。一方のビューシステムでは、個々のパーツを手動で制御して、望みどおりの新しい状態を実現しなければなりません。
状態が UI を制御するなら、どうすれば状態を更新して UI を更新できるのでしょうか。Compose では、イベントを通じてこれを実現します。ユーザーが UI 要素を操作すると、UI は onClick などのイベントを発行します。すると、イベント ハンドラが UI の状態を更新すべきかどうかを決定します。
UI の状態が変化すると、その状態に依存する関数(UI 要素)が再実行されます。状態が変化したときに UI が再生成される処理を、再コンポーズと呼びます。状態を UI に変換する処理と、UI を再生成する状態の変化は、Compose が UI フレームワークとして動作する仕組みの中核です。
先ほどの SurveyAnswer コンポーザブルの RadioButton は、操作しても未選択のままでした。この実装を更新して、タップしたときに選択状態が切り替わるようにしてみましょう
まず、selected という boolean 型の変数を定義し、それを RadioButton 関数の引数として渡します。さらに、onClick イベント ハンドラで selected 変数の値を現在の値から逆転させます。こうすることで、クリックしたときに選択状態が切り替わります。この変更を終えると、RadioButton をクリックして選択状態を切り替えられるようになります。
// SurveyAnswer.kt@Composablefun SurveyAnswer(answer: Answer) { Row { /* ... */ var selected: Boolean = // ... RadioButton(selected, onClick = { selected = !selected }) }}
/* ... */
var selected: Boolean = // ...
RadioButton(selected, onClick = {
selected = !selected
})
なお、実際のアプリでは、RadioButton の状態は SurveyAnswer に持たせるのではなく、Answer オブジェクトから取得します。この例は、RadioButton の状態の仕組みを説明することのみを目的としています。
また、selected 変数を実装していない点にも注意してください。これを動作させるには特殊な状態オブジェクトが必要ですが、この点は次の記事で説明します。
以上の内容をまとめます。Compose の考え方は次のとおりです。
ここでは、Compose の考え方をとても大まかに説明しましたが、学ぶべきことはまだまだたくさんあります。
たとえば、Kotlin の関数はどのように動作するのか、状態とはどのようなものか、Compose では他にどのようなコンポーネントが提供されるのかといったことです。こういった内容は、今後のエピソードで説明します。
それまで待てない方は、以下のリソースを確認してみてください。
質問がある方は、下にコメントを記入するか、Twitter でハッシュタグ #MADCompose をお使いください。10 月 13 日に予定されている本シリーズのライブ Q & A でお答えします。お楽しみに!
¹ データ バインディング ライブラリもビューで宣言的にコードを記述する方法の 1 つです。この仕組みは本記事で説明した状態同期問題を回避するために役立つ可能性がありますが、Compose を使うと XML を扱う必要は完全になくなります。
Reviewed by Mari Kawanishi - Developer Marketing Manager, Google Play
この記事は Kat Kuan による Android Developers Blog の記事 " Learn Jetpack Compose at a Compose Camp near you! " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。日本で開催される Compose Camp の詳細はこちらをご確認ください。
Jetpack Compose は、Android の UI 開発をシンプルにする Android の最新ツールキットです。Twitter、Airbnb (英語)、Google Play など、すでに世界中の多くのアプリで使われています。まだ使っていない方は、今が使い始める絶好のチャンスです。もっと簡単に Compose を学習していただくため、対面とバーチャルに対応したセッションとして、各地で Compose Camp (英語) を始めました。 (※ 日本で開催される Compose Camp の詳細はこちらをご確認ください。) Jetpack Compose を使って Android アプリを開発する方法を仲間と一緒に学ぶことができます。さっそく「キャンプ道具」を集めて、近くの Compose Camp (英語) に参加する方法を確認しましょう!
Jetpack Compose を使うと、コードの使用量と保守作業が少なくなるため、短期間でアプリを開発できます。また、API も直感的で強力なので、Android の最高の機能を使って優れたユーザー エクスペリエンスを提供できます。Google は、あらゆる人が Android 開発を学べるチャンスを増やせるようにする努力を続けており、その一環として、最新のベスト プラクティスをさまざまな学習スタイルで学んでいただけるようにしています。グループで学習を進めると、とても楽しく効果的に学べるという意見をたくさんの方からいただいています。それを受け、世界各地で Compose Camp (英語) を開催することにしました。ガイドしてくれる仲間や「キャンプ リーダー」のサポートを受けながら、Compose で Android アプリを開発する方法を学びましょう。
Android 開発が初めての方やプログラミングを始めたばかりの方は、Beginner トラックをご覧ください。基本的なプログラミングの考え方や、Jetpack Compose でユーザー インターフェースを作成する方法などのアプリ開発の基礎を学ぶことができます。
ビューから Compose に移行する方法を学びたい、あるいは高度な機能を使って UI を構築する方法を知りたいという Android デベロッパーには、Experienced トラックがおすすめです。Jetpack Compose のポイントから始めて、Compose のさまざまなトピックを掘り下げます。
他の人と一緒に学ぶと、コミュニティの一員としてアドバイスやサポートを受けられて楽しいという声がたくさんの方から寄せられています。Google デベロッパー コミュニティ (英語) は、同じ業界の学習者や仲間とつながりを持つ絶好のチャンスです。協力して技術的な難題に向かったり、お互いに技術を学び合って直接プロジェクトに活用したりできます。これからの数か月間、こういったコミュニティが世界中で Compose Camp を開催しますので、近くのイベントを探してみてください。 (※ 日本で開催される Compose Camp の詳細はこちらをご確認ください。)
イベントを開催して参加者に教えるのも、専門性を育む絶好のチャンスです。皆さん自身が「キャンプ リーダー」になる (英語) こともできます。学習に役立つ教材、セッションの進め方についてのガイド、サンプル スライド、参加グループを募るための資料など、Compose Camp を開催するために必要なものは、すべて揃えてあります。
「ソロキャンプ」の方が好きだという方は、自分のペースで学べるオンライン コースを確認しましょう。Android 開発を始めたばかりの方には、Android Basics with Compose コースがおすすめです。すでに Android 開発の知識をある程度お持ちの方は、Jetpack Compose for Android Developers コースをご覧ください。
ここで紹介したリソースが、Android 開発と Compose の学習に役立つことを願っています。Compose Camp (英語) で皆さんとお会いできるのを楽しみにしています。