この記事は、Google Developer Expert (GDE) @new_runnable (takahirom) さんに寄稿いただいたゲスト記事です。この「エキスパートに学ぶシリーズ」では、まだ Jetpack Compose を使ったことがないデベロッパー向けに既存アプリで段階的に導入を進める方法や導入するメリットについて GDE の方々よりご紹介いただきます。
みなさんの Android のアプリがまだ Jetpack Compose に移行されていない場合、RecyclerView を使っている部分もあるのではないでしょうか?
RecyclerView は効率的にリストなどのデータを表示する View で、今までの Android アプリの開発の中では頻繁に使われてきました。Jetpack Compose は View ベースの UI と統合することができ、View ベースのアプリに対しても段階的に導入が可能になっています。Jetpack Compose は RecyclerView を使っているアプリにも段階的に導入可能になっています。
RecyclerView から Jetpack Compose に書き直すことでかなりコードを単純化できます。この記事ではRecyclerView による実装をどうやって Jetpack Compose に置き換えていくかを紹介していきます。
置き換えるにはまずは「エキスパートに学ぶシリーズ : 1. Jetpack Compose を既存アプリに導入する」を読んで Jetpack Compose を導入していく必要があります。RecyclerView が使われていない画面であれば「エキスパートに学ぶシリーズ : 2. Jetpack Compose の段階的移行」を読んで、View をうまく移行していくのが良いと思います。
RecyclerView に関しては一気に Jetpack Compose に置き換えできればそれが一番簡単ですが、RecyclerView にたくさんの View が入ってしまっていて、一気に移行できない場合もあります。
そこで、今回は 2 つのステップに分けて移行する方法を紹介します。
簡単な RecyclerView でのコードをまず紹介し、そこからの移行の方法を紹介していきます。
こちらのサンプルは Android Studio の新しいバージョンを教えてくれるようなものをイメージしています。
RecyclerView で実装した場合は以下のような実装になるのではないかと思います。これを Jetpack Compose に置き換えていきます。
少しだけ各要素を紹介しておきます。
data class Article(val id: Int, val title: String)class ArticlesViewModel : ViewModel() { private val _articlesStateFlow = MutableStateFlow( listOf( Article(1, "🦊 Android Studio Arctic Fox is out!"), Article(2, "🐝 Android Studio Bumblebee is out!"), Article(3, "\uD83D\uDC3F️ Android Studio Chipmunk is out!"), Article(4, "\uD83D\uDC2C Android Studio Dolphin is out!"), ) ) val articlesStateFlow: StateFlow<List<Article>> = _articlesStateFlow.asStateFlow()}class MainActivity : ComponentActivity() { private val articlesViewModel by viewModels<ArticlesViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) val articlesAdapter = ArticlesAdapter() binding.recyclerView.adapter = articlesAdapter lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { articlesViewModel.articlesStateFlow.collect { articles -> articlesAdapter.submitList(articles) } } } }}class ArticlesAdapter : ListAdapter<Article, ArticlesAdapter.ArticleViewHolder>( DIFF_CALLBACK) { class ArticleViewHolder( private val binding: ItemArticleBinding ) : RecyclerView.ViewHolder(binding.root) { fun bindTo(article: Article) { binding.title.text = article.title } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder { return ArticleViewHolder(ItemArticleBinding.inflate(LayoutInflater.from(parent.context))) } override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) { holder.bindTo(getItem(position)) } companion object { val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Article>() { override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean { return oldItem == newItem } } }}activity_main.xml<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /></FrameLayout>item_article.xml<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dp" android:textAppearance="?attr/textAppearanceHeadline3" tools:text="test" /></LinearLayout>
data class Article(val id: Int, val title: String)
class ArticlesViewModel : ViewModel() {
private val _articlesStateFlow = MutableStateFlow(
listOf(
Article(1, "🦊 Android Studio Arctic Fox is out!"),
Article(2, "🐝 Android Studio Bumblebee is out!"),
Article(3, "\uD83D\uDC3F️ Android Studio Chipmunk is out!"),
Article(4, "\uD83D\uDC2C Android Studio Dolphin is out!"),
)
val articlesStateFlow: StateFlow<List<Article>> = _articlesStateFlow.asStateFlow()
}
class MainActivity : ComponentActivity() {
private val articlesViewModel by viewModels<ArticlesViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val articlesAdapter = ArticlesAdapter()
binding.recyclerView.adapter = articlesAdapter
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
articlesViewModel.articlesStateFlow.collect { articles ->
articlesAdapter.submitList(articles)
class ArticlesAdapter : ListAdapter<Article, ArticlesAdapter.ArticleViewHolder>(
DIFF_CALLBACK
) {
class ArticleViewHolder(
private val binding: ItemArticleBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bindTo(article: Article) {
binding.title.text = article.title
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
return ArticleViewHolder(ItemArticleBinding.inflate(LayoutInflater.from(parent.context)))
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
holder.bindTo(getItem(position))
companion object {
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Article>() {
override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem == newItem
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_height="match_parent"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</FrameLayout>
item_article.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:textAppearance="?attr/textAppearanceHeadline3"
tools:text="test" />
</LinearLayout>
この RecyclerView で表示している View の実装を Compose に移行していきましょう。ここでは RecyclerView 自体は置き換えず、中で表示している View を Compose に置き換えます。
まず RecyclerView のバージョンを 1.3.0-rc01、Compose UI のバージョンを 1.2.1 以降を使うようにします。これは Compose と RecyclerView が協調して動作するために必要です。 ※1
build.gradleimplementation 'androidx.recyclervew:recyclerview:1.3.0-rc01'
build.gradle
implementation 'androidx.recyclervew:recyclerview:1.3.0-rc01'
最初にさっそく Jetpack Compose で表示するコンポーズ可能な関数を用意しましょう。引数で Article クラスのインスタンスを受け取り、Text() を利用して内容を表示してあげます。ここでは単純に記事の内容を表示してあげるコンポーズ可能な関数になっています。
ここでは @Preview を使ってこのコンポーズ可能な関数の Preview も表示してあげています。
@Composablefun ArticleRow(article: Article, modifier: Modifier = Modifier) { Column(modifier) { Text( text = article.title, style = MaterialTheme.typography.h3, modifier = Modifier.padding(8.dp) ) }}@Preview@Composablefun ArticleRowPreview() { Mdc3Theme { Surface { ArticleRow( article = Article(1, "Hello ArticleRow"), modifier = Modifier.fillMaxWidth() ) } }}
@Composable
fun ArticleRow(article: Article, modifier: Modifier = Modifier) {
Column(modifier) {
Text(
text = article.title,
style = MaterialTheme.typography.h3,
modifier = Modifier.padding(8.dp)
@Preview
fun ArticleRowPreview() {
Mdc3Theme {
Surface {
ArticleRow(
article = Article(1, "Hello ArticleRow"),
modifier = Modifier.fillMaxWidth()
Android Studio 上の Preview 表示
RecyclerView 内で表示する View として ArticleRow() を表示するため、ArticleRowComposeView を作成します。
ArticleRowComposeView は AbstractComposeView を継承することで RecyclerView 内で View として Jetpack Compose を表示させます。
また、ArticleRowComposeView は Compose の State をフィールドとして持っています。これにより、RecyclerView の Adapter から、この Compose の State に記事データを入れ、コンポーズ可能な関数内でその State を読むことで記事データを Compose で表示させています。
class ArticleRowComposeView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0// AbstractComposeView を継承する) : AbstractComposeView(context, attrs, defStyle) { // Compose の State をフィールドとして持つ var article by mutableStateOf<Article?>(null) @Composable override fun Content() { Mdc3Theme { // Compose の State をコンポーズ可能な関数内で読む val article = article if (article != null) { ArticleRow( article = article, modifier = Modifier.fillMaxWidth() ) } } }}
class ArticleRowComposeView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
// AbstractComposeView を継承する
) : AbstractComposeView(context, attrs, defStyle) {
// Compose の State をフィールドとして持つ
var article by mutableStateOf<Article?>(null)
override fun Content() {
// Compose の State をコンポーズ可能な関数内で読む
val article = article
if (article != null) {
article = article,
RecyclerView とつなぎ込んで表示できるようにします。この作った ArticleRowComposeView を RecyclerView の Adapter の実装を変更して表示させます。
具体的には onCreateViewHolder() で ArticleRowComposeView を作成し、ArticleRowComposeView を持つ ViewHolder を作成します。
また Adapter.onBindViewHolder() から bindTo() の実装が呼ばれることにより ArticleRowComposeView に記事データを渡すことで Compose からそのデータを読み取ることができるようになります。
RecyclerView は名前の通り View を再利用 (Recycle) して表示するので、例えばスクロールで onCreateViewHolder() で一度作った ViewHolder とその中にある View を再利用し、 onBindViewHolder() でデータを再度入れる動きになります。
class ArticlesAdapter : ListAdapter<Article, ArticlesAdapter.ArticleViewHolder>( DIFF_CALLBACK) { class ArticleViewHolder( private val articleRowComposeView: ArticleRowComposeView ) : RecyclerView.ViewHolder(articleRowComposeView) { fun bindTo(article: Article) { articleRowComposeView.article = article } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder { val articleRowComposeView = ArticleRowComposeView(parent.context) return ArticleViewHolder(articleRowComposeView) } override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) { holder.bindTo(getItem(position)) }
private val articleRowComposeView: ArticleRowComposeView
) : RecyclerView.ViewHolder(articleRowComposeView) {
articleRowComposeView.article = article
val articleRowComposeView = ArticleRowComposeView(parent.context)
return ArticleViewHolder(articleRowComposeView)
Jetpack Compose Interop: Using Compose in a RecyclerView (英語) の記事でも触れられているようにスクロール時に毎回 Compose の Composition が破棄 (dispose) されないようにすることでパフォーマンスを改善することができます。
recyclerview:1.3.0-alpha02 より後のバージョンを使っていると RecyclerView と Compose が協調して動作するようになるため、スクロール中に頻繁に破棄されなくなります。
以下のように DisposableEffect を使ってログを出すことで、その挙動を確認することができます。もしスクロールしたときに頻繁に ItemRow xxx DISPOSED のログが出るようであれば、うまく RecyclerView のアップデートができていないか、間違ったタイミングで ComposeView を作ってしまっているなどの可能性があるので記事をみて詳細を確認してみてください。
@Composablefun ArticleRow(article: Article, modifier: Modifier = Modifier) { DisposableEffect(Unit) { Log.d("ArticleRow", "ItemRow ${article.id} composed") onDispose { Log.d("ArticleRow", "ItemRow ${article.id} DISPOSED") } }
DisposableEffect(Unit) {
Log.d("ArticleRow", "ItemRow ${article.id} composed")
onDispose { Log.d("ArticleRow", "ItemRow ${article.id} DISPOSED") }
ここまでで、RecyclerView の中身が Compose に置き換わりました。後は外側の RecyclerView を Jetpack Compose 置き換えることになります。
シンプルにここでは setContent{} を呼び出すことで Activity で Compose を表示するようにしています。この RecyclerView だけ移行したい場合などは、前回の記事が参考になると思います。
この 2 のステップによって、コード量がかなり少なくなっています。理由は XML のレイアウトを消せたことと、RecyclerView の Adapter のコードが LazyColumn に移行されたためです。
...// この 2 つが自動インポートできないことがあるようです。import androidx.compose.foundation.lazy.itemsimport androidx.compose.runtime.getValue...class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ArticlesScreen() } }}@Composableprivate fun ArticlesScreen(articlesViewModel: ArticlesViewModel = viewModel()) { Mdc3Theme { val articles by articlesViewModel.articlesStateFlow.collectAsState() LazyColumn { items( items = articles, key = { article -> article.id } ) { article -> ArticleRow( article = article, modifier = Modifier.fillMaxWidth() ) } } }}
...
// この 2 つが自動インポートできないことがあるようです。
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.getValue
setContent {
ArticlesScreen()
private fun ArticlesScreen(articlesViewModel: ArticlesViewModel = viewModel()) {
val articles by articlesViewModel.articlesStateFlow.collectAsState()
LazyColumn {
items(
items = articles,
key = { article -> article.id }
) { article ->
今回の実装について解説します。
ArticlesViewModel を viewModel() 関数を使って Jetpack Compose の中から取得しています。この viewModel() 関数を使うには以下の依存関係を追加する必要があります。
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1'
ここでは LazyColumn を利用してリスト表示をしています。LazyColumn は RecyclerView に似ていて必要なときだけ再コンポーズ (Recompose) などの最適化をしてくれます。また key の引数を渡しておくとさらに賢く最適化をしてくれます。詳しくは Use lazy layout keys (英語) で説明されています。
2 つのステップに分けて Jetpack Compose を移行していきました。移行した結果、コードはこの小さなサンプルでも 24 行ほど削減できたようです。また宣言的 UI により直感的にコードが書けるようになるなどのメリットも享受できます。Jetpack Compose に移行するメリットについて気になっている方は Compose を導入する理由にあるので確認してみてください。
git diff main compose --stat 4 files changed, 56 insertions(+), 80 deletions(-)
git diff main compose --stat
4 files changed, 56 insertions(+), 80 deletions(-)
このように Jetpack Compose では Java から Kotlin にしたようにアプリを段階的に移行できるようになっているので、少しずつ進めて開発効率を上げていきましょう。
※1 正確には RecyclerView 1.3.0-alpha02, Compose UI 1.2.0-beta02 以降で動きますが、記事を書いた時点で利用できるできるだけ安定したバージョンを書いています。将来的には RecyclerView 1.3.0 などもリリースされていくと思うので、なるべく新しいバージョンをご利用ください。
12 月 16 日に、DevFest Tokyo と Google の共催で「DevFest & Android Dev Summit Japan 2022」を開催します。
本イベントでは、10 月 25 日 (日本時間) に米国にて開始した Android Dev Summit (ADS) 2022 から「最先端の Android 開発 (MAD) 」「フォームファクター」「プラットフォーム」の 3 つの重要テーマに沿って、日本のデベロッパー向けに最新情報コンテンツをお届けします。
ADS の発表内容について、より理解を深めていただきやすいように Google 社員による振り返りや Q&A セッションをご用意しました。また国内のコンテンツや事例も多くご用意しており、 Google Developer Expert (GDE) によるビギナー向け Jetpack Compose の導入に関するセッションや Jetpack Compose を導入された株式会社メルカリ様や株式会社おいしい健康様、利用が拡大している大画面デバイス向けの UI 構築を実施された株式会社 U-NEXT 様にご登壇いただき、導入の背景や製品開発の経緯、導入後の率直な感想についてお話しいただきます。
一部のセッションをオンラインでも配信するのと同時に、ご希望に合わせて Google 東京オフィスの会場にご招待できるよう準備しています。会場では、タブレット、ウェアラブル、Chromebook、TV などのハードウェア端末を触って、アプリを体験できるデモブースや Google の技術担当社員と交流する機会を設けております。
また今回は DevFest イベントと共済になっており、 Android 以外にもウェブ、 Flutter や Firebase などのマルチプラットフォーム技術、機械学習などデベロッパー向けの幅広いコンテンツをご用意しておりますので、ご自身の興味・関心に合ったセッションにご参加ください。
オフライン (東京会場) 登録はこちら
*オフライン (東京会場) のご登録はイベントへの参加を確定するものではございません。参加が確定された方には、12 月初めまでに参加確定のご案内をお送りいたします。
オンライン視聴登録はこちら
皆さまのご参加をお待ちしております。
Posted by Mari Kawanishi - Developer Marketing Manager, Google Play
この記事は、 Google Developer Expert (GDE) @yanzm さんに寄稿いただいたゲスト記事です。この「エキスパートに学ぶシリーズ」では、まだ Jetpack Compose を使ったことがないデベロッパー向けに既存アプリで段階的に導入を進める方法や導入するメリットについて GDE の方々よりご紹介いただきます。前回は既存アプリに Jetpack Compose を導入する手順について紹介しました。今回は XML レイアウトの中に Jetpack Compose による UI を表示するための View を追加し、段階的に Jetpack Compose に置き換えていく方法を紹介します。
まず、カンファレンスのセッション情報を表示するためのレイアウトを例に、XML で定義されたレイアウトを Jetpack Compose へ置き換えて行きます。
レイアウトは次の様に ConstraintLayout を使って構成されています。その中には、セッションタイトル、スピーカーの画像、スピーカーの名前、スピーカーの肩書きを表示する 3 つの TextView と 1 つの ImageView が含まれます。
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/session_title" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:layout_marginEnd="16dp" android:textAppearance="@style/TextAppearance.AppCompat.Headline" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="Jetpack Compose で Material Design 3" /> <ImageView android:id="@+id/speaker_image" android:layout_width="72dp" android:layout_height="72dp" android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:layout_marginBottom="16dp" android:background="#cccccc" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/session_title" /> <TextView android:id="@+id/speaker_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:textAppearance="@style/TextAppearance.AppCompat.Body1" app:layout_constraintBottom_toTopOf="@id/speaker_title" app:layout_constraintStart_toEndOf="@id/speaker_image" app:layout_constraintTop_toTopOf="@id/speaker_image" app:layout_constraintVertical_chainStyle="packed" tools:text="Yuki Anzai" /> <TextView android:id="@+id/speaker_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:textAppearance="@style/TextAppearance.AppCompat.Caption" app:layout_constraintBottom_toBottomOf="@id/speaker_image" app:layout_constraintStart_toStartOf="@id/speaker_name" app:layout_constraintTop_toBottomOf="@id/speaker_name" tools:text="Android GDE" /></androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/session_title"
android:layout_width="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Jetpack Compose で Material Design 3" />
<ImageView
android:id="@+id/speaker_image"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_marginBottom="16dp"
android:background="#cccccc"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/session_title" />
android:id="@+id/speaker_name"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
app:layout_constraintBottom_toTopOf="@id/speaker_title"
app:layout_constraintStart_toEndOf="@id/speaker_image"
app:layout_constraintTop_toTopOf="@id/speaker_image"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Yuki Anzai" />
android:id="@+id/speaker_title"
android:layout_marginTop="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
app:layout_constraintBottom_toBottomOf="@id/speaker_image"
app:layout_constraintStart_toStartOf="@id/speaker_name"
app:layout_constraintTop_toBottomOf="@id/speaker_name"
tools:text="Android GDE" />
</androidx.constraintlayout.widget.ConstraintLayout>
このレイアウトを持つアプリは MDC-Android (英語) の マテリアル デザイン 3 テーマをベースとするアプリのテーマを View システムで使っています。
そのため Jetpack Compose 用の マテリアル デザイン 3 コンポーネントライブラリに加えて、既存 View システムで使用しているテーマを Jetpack Compose で再利用するための MDC-Android Compose Theme Adapter (英語) のマテリアル デザイン 3 用ライブラリを追加します。
dependencies { … implementation "androidx.compose.material3:material3:1.0.0" implementation "com.google.android.material:compose-theme-adapter-3:1.0.21"}
dependencies {
…
implementation "androidx.compose.material3:material3:1.0.0"
implementation "com.google.android.material:compose-theme-adapter-3:1.0.21"
XML レイアウトの中に Jetpack Compose による UI を表示するには、 ComposeView という View を利用します。
XML レイアウトの中に ComposeView を追加して android:id を指定します。
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout …> <androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/session_title" … /> …</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout …>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
app:layout_constraintTop_toTopOf="parent" />
… />
ComposeView には setContent メソッドが用意されています。このメソッドのラムダブロック内に Jetpack Compose を利用したコードを記述して UI を実装していきます 。
class SessionDetailActivity : AppCompatActivity() { private val binding by lazy { ActivitySessionDetailBinding.inflate(layoutInflater) } private val viewModel: SessionDetailViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) … binding.composeView.setContent { // この中に Jetpack Compose のコードを書いていく Mdc3Theme { Text("Hello, Compose!") } } }}
class SessionDetailActivity : AppCompatActivity() {
private val binding by lazy {
ActivitySessionDetailBinding.inflate(layoutInflater)
private val viewModel: SessionDetailViewModel by viewModels()
binding.composeView.setContent {
// この中に Jetpack Compose のコードを書いていく
Text("Hello, Compose!")
XML レイアウトの置き換え先になるコンポーズ可能関数を SessionDetail という名前で用意します。以下、 SessionDetail Composable の様な記述は、 SessionDetail という名前のコンポーズ可能関数を意味します。 SessionDetail Composable に表示させるセッションの情報は、SessionData 型のオブジェクトを引数でとるようにします。
Jetpack Compose では Preview アノテーションを使って Composable のプレビューを表示することができるので、 SessionDetail Composable のプレビューも用意しておきます。
@Composableprivate fun SessionDetail(data: SessionData) {}@Preview@Composableprivate fun SessionDetailPreview() { Mdc3Theme { Surface { SessionDetail( data = SessionData( … ) ) } }}
private fun SessionDetail(data: SessionData) {
private fun SessionDetailPreview() {
SessionDetail(
data = SessionData(
SessionData は SessionDetailViewModel が保持しており、Flow<SessionData> 型で公開されています。
class SessionDetailViewModel : ViewModel() { val sessionDataFlow: Flow<SessionData> = …}
class SessionDetailViewModel : ViewModel() {
val sessionDataFlow: Flow<SessionData> = …
Jetpack Compose には Flow を State に変換する collectAsState メソッドが用意されています。このメソッドを使って Flow<SessionData> を State<SessionData?> に変換します。State の値に value プロパティでアクセスし、 SessionDetail Composable に渡します。
SessionDetailViewModel ではなく SessionDetail を渡すようにすることで、 SessionDetail Composable を stateless(状態を持たない)な Composable にできます。stateless な Composable はテストがしやすくプレビューも簡単にできます。
class SessionDetailActivity : AppCompatActivity() { … private val viewModel: SessionDetailViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) … binding.composeView.setContent { // この中に Jetpack Compose のコードを書いていく Mdc3Theme { val state: State<SessionData?> = viewModel.sessionDataFlow .collectAsState(null) val data: SessionData? = state.value if (data != null) { SessionDetail(data = data) } } } }}
val state: State<SessionData?> = viewModel.sessionDataFlow
.collectAsState(null)
val data: SessionData? = state.value
if (data != null) {
SessionDetail(data = data)
セッションタイトルを表示する Text Composable を SessionDetail Composable に追加します。セッションタイトルを表示していた TextView は削除するのではなく android:visibility を invisible にします。
スピーカーの画像を表示する ImageView がこの TextView を参照しているため、削除すると ImageView のレイアウトでエラーがおこり、参照先を変更しなければなりません。また android:visibility が gone の場合だと TextView のエリア分だけ ImageView が上にずれてしまいます。
@Composableprivate fun SessionDetail(data: SessionData) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp), ) { Text( text = data.sessionTitle, style = MaterialTheme.typography.headlineSmall, ) }}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
text = data.sessionTitle,
style = MaterialTheme.typography.headlineSmall,
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout …> <androidx.compose.ui.platform.ComposeView … /> <TextView android:id="@+id/session_title" … android:visibility="invisible" … /> …</androidx.constraintlayout.widget.ConstraintLayout>
android:visibility="invisible"
インターネット上にある画像を Compose で表示する例として、ここでは Coil ライブラリ (英語) を使います。
dependencies { … implementation "io.coil-kt:coil-compose:2.2.2"}
implementation "io.coil-kt:coil-compose:2.2.2"
Coil ライブラリ の AsyncImage Composable を使ってスピーカーの画像を表示します。
ConstraintLayout 内の ImageView は android:visibility を invisible にしておきます。
@Composableprivate fun SessionDetail(data: SessionData) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp), ) { Text( text = data.sessionTitle, style = MaterialTheme.typography.headlineSmall, ) Row( modifier = Modifier.padding(top = 16.dp), verticalAlignment = Alignment.CenterVertically, ) { AsyncImage( model = data.speakerImageUrl, contentDescription = "speaker image", modifier = Modifier.size(72.dp) ) } }}
Row(
modifier = Modifier.padding(top = 16.dp),
verticalAlignment = Alignment.CenterVertically,
AsyncImage(
model = data.speakerImageUrl,
contentDescription = "speaker image",
modifier = Modifier.size(72.dp)
スピーカーの名前と肩書きも同じように Text Composable で表示します。
@Composableprivate fun SessionDetail(data: SessionData) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp), ) { Text( text = data.sessionTitle, style = MaterialTheme.typography.headlineSmall, ) Row( modifier = Modifier.padding(top = 16.dp), verticalAlignment = Alignment.CenterVertically, ) { AsyncImage( model = data.speakerImageUrl, contentDescription = "speaker image", modifier = Modifier.size(72.dp) ) Column( modifier = Modifier.padding(start = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { Text( text = data.speakerName, style = MaterialTheme.typography.bodyMedium, ) Text( text = data.speakerTitle, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } }}
modifier = Modifier.padding(start = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
text = data.speakerName,
style = MaterialTheme.typography.bodyMedium,
text = data.speakerTitle,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
ConstraintLayout 内の全ての View を SessionDetail Composable に置き換えたので、 XML レイアウトおよび View Binding をやめて Activity の setContent を使うようにします。
class SessionDetailActivity : AppCompatActivity() { private val viewModel: SessionDetailViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Mdc3Theme { val state: State<SessionData?> = viewModel.sessionDataFlow .collectAsState(null) val data: SessionData? = state.value if (data != null) { SessionDetail(data = data) } } } }}
XML レイアウトを段階的に Jetpack Compose に置き換えていく方法を紹介しました。ComposeView を使うことで View 階層の中に Jetpack Compose による UI を簡単に組み込むことができます。
表示内容の変わらない View ならデータの更新について考える必要がないため、 Jetpack Compose への初めての置き換えに最適です。ぜひ挑戦してみてください。
この記事は Lauren Mytton による Android Developers Blog の記事 " Raising the bar on technical quality on Google Play " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
アプリの品質は、Google Play が行うあらゆる施策の基盤です。Android ユーザーは、ダウンロードするアプリやゲーム上での優れた体験を期待しています。そのため、Google Play ストアでは、品質の高いタイトルをより見つけやすくするとともに、品質の低いタイトルからユーザーの目を遠ざけています。ユーザーの期待に応えるため、私たちの品質基準を満たさない一部のアプリやゲームは、「おすすめ」などの目立つ発見手段から除外されたり、ストアの掲載情報に警告が表示されることがあります。
アプリの品質を高め、より多くのユーザーを惹きつけるための最も重要な方法の 1 つは、技術的な品質に注力することです。この記事では、Google Play が技術的な品質の定義をよりユーザーの体験に沿ったものに進化させることを説明し、デベロッパーの皆さんが、技術的な問題や機会を監視測定し、対処するために役立つ Android Vitals の新機能を紹介します。
Android Vitals は、現場から上がってくる安定性やパフォーマンスの指標など、Google Play での技術的品質をモニタリングするための 1 番の近道です。最も重要な指標は core vitals と呼ばれ、Google Play ストアでのアプリの可視性に影響します。
今後、既存の core vitals の指標を、よりユーザーに焦点を当てた新しい指標に置き換える予定です。私たちは、これらの指標が、ユーザー エクスペリエンスを反映していると信じており、アンインストールとの相関もより強いことが確認されています。
全体的な品質を向上するために、引き続き全体的な不正な動作のしきい値を適用します。全体的なしきい値は新しい指標を使用しますが、ユーザーが認識したクラッシュ率は 1.09%、ユーザーが認識した ANR 率は 0.47% のままで変更されません。Google Play でタイトルの可視性を最大化するには、これらのしきい値を下回るようにすることが重要です。
技術的な品質は、デバイス間によって異なることがあります。例えば、あるスマートフォンのデバイスではアプリが安定していてスムーズでも、別のデバイスではそうでないことがあります。これを考慮し、スマートフォンのモデル(例:Pixel 6)ごとに評価される新しい不正な動作のしきい値を導入します。ローンチ時には、このしきい値は、ユーザーが認識したクラッシュ率とユーザーが認識した ANR 率の両方について 8% に設定される予定です。
皆さんのタイトルが特定のスマートフォン モデルの core vitals でデバイスの不正な動作のしきい値を超えた場合、Google Play はそのスマートフォン モデルのユーザーに対するタイトルの可視性を低下する場合があります。たとえば、一部の 検出サーフェスからタイトルを除外したり、場合によっては、ストアの掲載情報に警告を表示して、アプリがお使いのスマートフォンで正常に動作しない可能性があることをユーザーに示すことがあります。Google Play ストアは、2022 年 11 月 30 日からストアの掲載情報の警告の適用を開始する予定です。
2022 年 11 月 30 日以降、ユーザーが所有するデバイスにおいて、ユーザーが認識したクラッシュ率またはユーザーが認識した ANR 率が 8% を超えるタイトルがある場合、ストアの掲載情報で警告が表示される場合があります。なお、警告文は変更される可能性があります。
この新しい品質ガイドラインに対応するため、Android Vitals では、問題の監視と対処を容易にするためのさまざまな新機能をリリースしました。
新しい指標は、Google Play Console の Android Vitals で利用できるほか、Play Developer Reporting API (英語) も利用できます。既存の指標は、引き続きコンテキストとして利用できます。
Android Vitals で、全体的な不正な動作のしきい値に対するタイトルの状態をモニタリングすることができます。さらに、Android Vitals は、デバイスごとのしきい値を超えている場合は警告を表示します。また、どのデバイスで、何人のユーザーに影響を及ぼしているかも表示されます。
ユーザーが認識したクラッシュ率や ANR 率に対する技術的な品質問題の原因を理解するには、これらの率の原因となるクラッシュや ANR のクラスターを調べましょう。これらは、Android Vitals の [クラッシュと ANR] ページで、全体または特定のスマートフォン モデルについて見ることができます。多くのユーザーに影響を及ぼしている問題から順番に対処することで、問題発生率をより迅速に低減することができます。
デバイスのハードウェアやソフトウェアの特定の側面が、問題発生率に影響することがあります。このような問題を発見し解決するために、Android Vitals に新しい機能が追加され、関連性が見つかった場合に通知されるようになりました。また、[リーチとデバイスの概要]ページで、ご自身で関連性を調べることも可能です。
Android Vitals は、デバイスのハードウェアまたはソフトウェアに関連する可能性のある問題を警告します。
デバイスごとに取り組むべき品質の問題の優先順位を決める際には、既存ユーザーと、新規ユーザーに対する機会損失や影響の両方を考慮する必要があります。これを支援するために、インストールベース、収益、評価、レビューなどの統合デバイス情報へのワンクリックでのアクセスを Google Play Console 上で開始しました。この情報はサイドパネルに表示されるため、今見ているページを離れる必要はありません。
Google Play Console の新しいサイドパネルでは、統合されたデバイス情報を
コンテキストで提供します。
Android Vitals に関連のある方々全員にアクセス権を付与してください。新しい品質指標は Android Vitals で公開されており、全体的またはスマートフォンごとの技術的な品質問題が表示されます。Google Play Console で Android Vitals を定期的に確認するか、Play Developer Reporting API (英語) と統合して、データを、ご自身のワークフローに直接取り込むことをお勧めします。詳細については、Android Developer のサイト を参照してください。
しきい値は 8% からスタートしますが、デベロッパーの皆さんには、スマートフォンごとの安定性指標が 2% 以下になることを目指していただきたいと思います。このことを念頭に置き、2023 年の前半には、スマートフォンごとのしきい値が低くなる可能性があることを想定してください。将来的には、ユーザー エクスペリエンスの他の重要な側面を反映する新しい指標を追加するために、品質基準を拡張する可能性もあります。その場合も、事前にお知らせします。
ユーザーは端末上での素晴らしい体験を期待し、その期待に応えることができるデベロッパーの皆さんは Google Play でのさらなる成功を収めるでしょう。これらのアップデートは、ユーザーとデベロッパー両方の方々が最悪の体験を回避できるように設計されていますが、私たちの長期的な焦点は、優れたユーザー エクスペリエンスを増やすことです。そのためには、クラッシュや ANR 以外の技術的な品質への投資と、品質に関する非技術的な側面への投資が必要です。今後数か月間にわたり、このトピックの続報をお知らせする予定ですので、ご注目ください。
Reviewed by Maru Maruyama - Developer Relations Engineer, Tamao Imura - Developer Marketing Manager, Platforms and Ecosystems
この記事は Android Developers Team による Android Developers Blog の記事 " Outdooractive boosts user experience on wearable devices with 30% less development time using Compose for Wear OS " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
Outdooractive (英語) はヨーロッパ最大のアウトドア プラットフォームで、1,200 万人 (英語) を超える自然愛好家のグローバル コミュニティにトレイルマップや情報を提供しています。アウトドア活動の計画やナビゲーションに特化したプラットフォームである Outdooractive は以前より、ユーザー数の拡大にはスマートウォッチなどのウェアラブル デバイスが欠かせないと考えていました。ウェアラブルは、ナビゲーション ツールやアクティビティ トラッカーとしてユーザーに大きな価値を提供します。そのため、Outdooractive のデベロッパーは、Google から Android の新しい UI ツールキット Wear OS 向け Compose (英語) に関する打診を受けたとき、この成長市場でアプリを改善する絶好の機会だと考えました。
アプリの再構築に着手すると、Wear OS 向け Compose のメリットはすぐに明らかになりました。Outdooractive のデベロッパーは、開発時間を 30% ほど短縮でき、効率的なユーザー インターフェースを短時間で作成できるようになりました。Outdooractive のシニア プロジェクト マネージャーである Liam Hargreaves 氏は、「数日かかりそうなものが、数時間でできるようになりました」と話します。
コードベースの最新化と開発スピードの向上によって、デベロッパーが UI のコードを直感的に読み書きできるようになりました。また、デザイン フェーズでのプロトタイピングが高速になり、柔軟なコラボレーションができるようになりました。そのため、デベロッパーは、ユーザーのためにさらに便利な機能を作れるようになっています。
Outdooractive のアプリが目指しているのは、正確な情報をユーザーのウェアラブル デバイスまでリアルタイムに届けることです。たとえば、曲がるべき場所を伝えるナビゲーション、トレイルの状態、最新の気象情報といった情報です。
「私たちのアプリには、かなり複雑なインタラクションがあります」と Hargreaves 氏は言います。「こういった情報を、アクセスしやすい形で、シンプルかつ高速にわかりやすく提示しなければなりません。しかも、お客様は山道にいたり、嵐に遭遇していたり、手袋や冬用のハイキング装備を身につけていたりするかもしれません」
Outdooractive のデベロッパーは、Wear OS 向け Compose の新機能を活用して、移動中のユーザーに高品質な体験を提供できるアプリを作成しました。Chip コンポーネントを使うことで、リスト作成のプロセスを大きく改善できました。あらかじめ用意されているデザイン要素を使えるので、デベロッパーは数日分の作業を省略できました。また、ScalingLazyColumn を使うことで、RecyclerView や ScrollView に頼ることなく、最適なスクロール画面を作成できました。
AnimatedVisibility コンポーネントが使いやすいうえに、これまでは開発する余裕がなかったアニメーション機能を作成できるようになったことも、デベロッパーを喜ばせました。Wear OS 向け Compose で特に重宝されたのは、「読み込み中」や「エラー」など、さまざまな UI の状態をとても簡単にユーザーに提示できるようになったことでした。
「Compose を使うと、UI コードを直感的に読み書きできるようになります。デザイン フェーズでのプロトタイピングが高速になり、コードでのコラボレーションもしやすくなりました」
Wear OS 向け Compose を導入したことで、Outdooractive のユーザーは、ハイキング コースのナビゲーションなど、通常であればスマートフォンで行っていたことをウェアラブル デバイスで行えるようになりました。これは、Wear OS 向け Compose のおかげで実現した主要な UI の目標でした。
「ウェアラブルは、私たちのプロダクト戦略や市場戦略において欠かせない部分の 1 つです。ユーザーからの反応も非常に好意的です」と Hargreaves 氏は語っています。
Outdooractive のデベロッパーは、心拍数のモニタリングなどのフィットネス トラッキング機能をアプリに組み込むことで、ウェアラブル デバイスならではの機能も提供しています。これは、Health Services という別の Wear OS 機能を使い、デバイスに搭載されたセンサーにアクセスして実現しています。Wear OS の Health Services は、健康やフィットネスに関連するセンサーの設定をすべて自動で行い、データを収集し、心拍数や移動距離、速度などの指標を計算します。そのため、デベロッパーは電池寿命をできる限り延ばしながら、高度なアプリの機能を簡単に実現できます。Outdooractive のデベロッパーは、Health Services と Wear OS 向け Compose を活用し、ボディセンサーを使ってアプリの機能をさらに強化したいと考えています。
Outdooractive の効率的なプロセスは、Wear OS 向け Compose によってアプリ開発が簡単になるという実例を示しています。これが実現できたのは、デベロッパーの柔軟性が向上し、開発の負担を増やさずにさまざまなレイアウトを試せるようになったからです。
Hargreaves 氏は、Wear OS 向け Compose を使うことを検討している他のデベロッパーに向けて、「全面的に導入することをお勧めします」と明確にアドバイスしています。
Wear OS 向け Compose を使うと、ウェアラブル デバイスでエンゲージメントの高いユーザー エクスペリエンスを構築できます。
概要を知りたい方は、Google I/O の Wear OS 関連トーク (動画/英語 - 日本語字幕は、YouTube の自動字幕機能から日本語を選択してください) をご覧ください。さっそく学習を始めたい方は、Wear OS 向け Compose の Codelab をお試しください。
この記事は Paul Feng による Android Developers Blog の記事 " Continuing our Commitment to User Choice Billing" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
ユーザーとデベロッパーの皆さんにより多くの柔軟性と選択肢を提供するために継続的に進化してきた Android の長い歴史に沿って、ユーザー選択型決済の試験運用プログラムを通じて、Google Play の課金オプションの拡大を検討し始めることを今年初めに発表しました。アプリ内課金用の代替的な課金システムを提供するには、ユーザーの手に委ねることが最良だという信念が、この試験運用プログラムの根底にあります。
試験運用プログラムに参加されている方々は、所定の国のユーザーに対して、Google Play の課金システムに加えて、その他の代替的な課金システムを提供できるようになります。私たちのゴールは、安全でポジティブなユーザーエクスペリエンスを維持しながら、世界中のデベロッパーとユーザーの皆さんが、ユーザー選択型決済をサポートする際に伴う複雑さを理解することです。この試験運用プログラムは、私たちが、さまざまな実装をテストして繰り返したり、デベロッパーやユーザーからプログラムの体験に関する洞察を集めることで、このプログラムをどのように進化できるかを決断できます。
ユーザー選択型決済の試験運用プログラムの詳細はこちら
この試験運用プログラムを発表した際に、最初のパートナーとして Spotify と協業することをお伝えしました。私たちは、Spotify と密接に連携をしており、今週からユーザー選択型決済の初期テスト導入が一部の国の Spotify ユーザーに対して開始されたことを嬉しく思っています。私たちは、Spotify での反復を通して、この体験がさらに進化していくと信じています。この取り組みの展開の詳細については、こちらの Spotify からの発表をご覧ください。
また、Bumble がユーザー選択型決済の試験運用プログラムに参加してくれたことも嬉しい限りです。今後数か月中に、一部の国でアプリ内での選択が可能になるものと思われます。
さらに、世界中のデベロッパーからの関心を受け、2022 年 9 月には、ゲーム以外のアプリを開発するすべてのデベロッパーの皆さんの試験運用プログラムへの参加が可能となりました。現時点での UX ガイドラインを含む参加条件や要件の詳細を説明し、ユーザー選択型決済をまずオーストラリア、インド、インドネシア、日本、欧州経済地域のユーザーに提供することを発表しました。
デベロッパーやユーザーの皆さんからの好意的な反応と初期のフィードバックに基づき、アメリカ、ブラジル、そして、南アフリカのユーザーにもこの試験運用を拡大することを 2022 年 11 月 11 日 (日本時間) に発表しました。
参加しているデベロッパーは、35 カ国以上でユーザー選択型決済の展開が可能
この試験運用プログラムはまだスタートしたばかりですが、私たちは、開始直後にいただいた反応に勇気づけられています。今後、パートナーの皆さんとの協力関係の構築を継続しながら、より多くのユーザーにユーザー選択型決済を展開し、さらに多くの情報を共有できることを楽しみにしています。試験運用プログラムの参加条件、要件、参加方法などの詳細については、ヘルプセンターをご覧ください。
Reviewed by Tamao Imura - Developer Marketing Manager, Platforms and Ecosystems
この記事は Matthew McCullough による Android Developers Blog の記事 " What’s new from Android, at Android Dev Summit ‘22 " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
アメリカのベイエリアで Android Dev Summit (英語) が始まりました。そこでは、皆さんが最新の Android 開発を活用して、ユーザー向けのすばらしい体験を構築するたくさんの方法を紹介しました。最新の Android 開発は、リストサイズからタブレットや折りたたみ式の大画面まで、Android が提供するさまざまな画面サイズのデバイスでアプリの拡張を支援しています。
ここでは、内容をまとめて紹介します。基調講演全編 (動画/英語) も忘れずにご覧ください!
Android Dev Summit ‘22 基調講演
* 日本語字幕は、YouTube の字幕機能から日本語を選択してください
最新の Android 開発、略して MAD (Modern Android Development) と呼んでいる一連のライブラリ、ツール、サービス、ガイドを導入したのは数年前のことでした。Android Studio、Kotlin、Jetpack ライブラリ、そして強力な Google & Play 開発者サービスで私たちが目指しているのは、あらゆる Android デバイスに対応した高品質なアプリを速く簡単に開発できるようにすることです。
何年か前には、美しく高度な UI を作れるように Jetpack Compose を導入しました。これは、新しい Android アプリの推奨 UI フレームワークになっています。
また、安定版の Compose ライブラリを指定した Gradle 部品構成表 (BOM) を導入しています。最初の BOM リリースとなる 22 年 10 月版 Compose (英語) には、マテリアル デザイン 3 コンポーネント、Lazy スタッガード グリッド、可変フォント、プルリフレッシュ、遅延リストのスナップ、キャンバスでのテキスト描画、テキストの URL アノテーション、ハイフネーション、LookAheadLayout が含まれています。Lyft のチームは、Compose を使うことで大きなメリットを実現し、「今では、すべての新機能のコードの 90% 以上を Compose で開発しています」 (英語) と話しています。
私たちは、Android のデバイス エコシステム全体を活用してもらうために Compose を役立てたいと考えています。Compose for Wear OS は、数週間前に 1.0 の安定版リリースに到達し、Wear OS での推奨の UI 開発方法になりました。2022 年 10 月 24 日は、さらにこの流れを拡大するため、Compose for Android TV の最初のアルファ版をリリースしました。フィーチャー カルーセルやイマーシブ リストなどのコンポーネントはすでに利用でき、近日中にさらに多くのコンポーネントが追加される予定です。Android を学習している方や新しいアプリを作ろうとしている方には、Jetpack Compose がお勧めです!
最新の Android 開発を実現しているのが、Android Studio (英語) です。公式 IDE である Android Studio には、あらゆる種類の Android デバイスに対応したアプリを開発するための強力なツールが搭載されています。22年 10 月版 Compose では、皆さんが試すことができるたくさんの新機能をリリースしています。デフォルトが Compose でマテリアル 3 を採用している更新版テンプレート、デフォルトでの Compose ライブ編集の有効化、コンポジションのトレース、Android SDK アップグレード アシスタント、App Quality Insights の改善などです。すべての機能は、Android Studio Flamingo の最新プレビュー版をダウンロード (英語) して試すことができます。ぜひ、フィードバックをお送りください。
今、ユーザーが注目しているのは、最も小さく身近なデバイス、つまりスマートウォッチです。昨年には、Samsung との合同プラットフォーム Wear OS を発表しました。そして今年は、Samsung Galaxy Watch 5 や Google Pixel Watch といったすばらしい新デバイスが登場したことで、デバイスのアクティベーション数が 3 倍になっています。Wear OS アプリの開発を速く簡単にする Compose for Wear OS は、この夏に 1.0 になり (英語)、Wear OS アプリで推奨の UI 開発方法になっています。20 を超えるウェアラブル専用 UI コンポーネントがデザインされており、マテリアル テーマやユーザー補助機能も組み込まれています。
2022 年 10 月 24 日に、Android Studio の Wear OS 向けテンプレートと、Wear OS の安定版 Android R エミュレータ システム イメージが更新されたことをお知らせします。
ウェアラブルからはパーソナライズされたデータが得られるので、データを公開せず、完全に安全な状態に保つことが重要です。そこで、それを簡単に実現するソリューションとして、ヘルスコネクトに取り組んできました。健康データを格納して共有する API は、Samsung と密接に連携して開発しました。ユーザーはこの 1 か所のみで、簡単にパーミッションを管理できます。
Wear OS に注力しているデベロッパーは、大きな成果を挙げています。Todoist は、アプリを Wear 3 向けに再構築して、インストールの増加率が 50% 上昇しました。また、Outdooractive は Compose for Wear OS を使って開発時間を 30% 短縮しました。他にはない魅力的な体験を Wear OS ユーザーに届けるなら、今が狙い目です!
* 日本語字幕は、YouTube の自動字幕機能から日本語を選択してください
以前もお知らせしたように、Google は全力を挙げてタブレット、折りたたみ式、ChromeOS に注力しています。Samsung Galaxy Z Fold4、Lenovo P12 Tab Pro、今後発売予定の Google の Pixel Tablet など、すばらしい新ハードウェアが登場する今こそ、アプリを見直して大画面に対応する絶好のチャンスです。私たちは、Android のアップデート、Google アプリの改善や Google Play ストアの変更といった作業に懸命に取り組んでおり、タブレットに最適なアプリを見つけやすくしています。
Android Studio Electric Eel では、サイズ変更可能なエミュレータとデスクトップ エミュレータ、どんなサイズの画面でもベスト プラクティスに従えるようにするためのビジュアル lint チェックなどを通して、これまでになく簡単にアプリを大画面でテストできるようにしています。
さらに、こういったデバイス向けのデザインやレイアウトのガイドを増やしてほしいという声も寄せられていることから、2022 年 10月 24日、developer.android.com を縦断した (英語) 新しいアプリ レイアウト ガイドと、正規レイアウト (英語) 向けのデベロッパー ガイドとサンプルを追加しました。
大画面機能に対応すると、アプリのエンゲージメントが向上します。たとえば Concepts (英語) の場合、描画機能や図形ガイドなどの優れたタッチペン操作を ChromeOS とタッチペン デバイスで実現したことで、タブレットでの使用がスマートフォンに比べて 70% 高くなりました。
Android Studio や Window Manager Jetpack などの改善に関する最新情報は、11 月 9 日にライブ中継されるフォーム ファクタ トラック (英語) をご覧ください。
成功につながるプラットフォームの中心となるのはオペレーティング システムです。そして、8 月にリリースされた Android 13 では、パーソナライズ、プライバシー、セキュリティ、接続性、メディアなど、プラットフォームの実にさまざまな面で機能強化が行われています。
たとえば、アプリ別の言語設定では、多言語ユーザーの操作が改善され、状況に応じてデバイスの言語を使い分けられるようになります。
パーミッションが不要な新しい写真選択ツールでは、ユーザーが写真や動画を閲覧して選択できますが、ユーザーが明示的にアプリと共有することを選んだものだけが対象になります。これは、Android がプライバシーを重視していることを示す好例です。
また、新しい API レベルをターゲットにしやすくするため、最新の Android Studio Flamingo のプレビュー版に Android SDK アップグレード アシスタント ツールを導入しています。特に重要な変更点について、手順が細かく記載されたドキュメントが提供されるので、アプリのターゲット SDK をアップデートする際に参考にできます。
ここで紹介したのは、ほんの一例に過ぎません。私たちは、Android が提供する最新機能 (英語) を活用できるようにしつつ、プラットフォームの変更にアプリを対応する作業をこれまで以上に簡単にしています。
プラットフォームについて知っておきたい 3 つのこと
Android Dev Summit の初日に基調講演を行い、最新の Android 開発に関する最初のトラック (英語) が始まりましたが、今後もまだまだ続きます。11 月 9 日には、次のトラック:フォーム ファクタ (英語) がライブストリームされるので、お楽しみに。最後のテクニカル トラックはプラットフォーム (英語) についてで、11 月 14 日にライブストリームされます。
どうしても聞いてみたい質問がある方は、#AskAndroid を使ってツイートしてください。毎回のトラックのライブストリームの最後には、ライブ Q&A でチームが皆さんの質問に回答しますので、ぜひご覧ください。
大変うれしいことに、今年は世界中のデベロッパーの皆さんと直接お会いできる機会があります。本日のベイエリアはその初回です。11 月 9 日には、Android Dev Summit をロンドンからお届けします。お楽しみは 12 月のアジアまで、各地で続きます。12 月 16 日には DevFest & Android Dev Summit Japan 2022 が東京で(詳細は後日)、12 月中旬にはバンガロールで行われます(参加希望はこちら (英語) から登録できます)。
Android をより良いプラットフォームにするためには、オンラインで参加する方も世界各地の会場に直接お越しになる方も含め、デベロッパーの皆さんのフィードバックが必要です。皆さんと一緒に優れたアプリを開発し、Android が提供するさまざまなデバイスでユーザーを喜ばせる機会をいただけたことに感謝します。どうぞ 2022 Android Dev Summit をお楽しみください!
この記事は Tom Grinsted による Android Developers Blog の記事 " Supporting and rewarding great Apps and Games on Google Play " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
Google Play の使命は、Android ユーザーとアプリやゲームのデベロッパーの皆さんとの関係を促進し、世界中の何万ものビジネスがモバイル エコシステムで成長し繁栄することを可能にすることです。私たちは毎日、何十億ものユーザーが端末上で魅力的で役立つ豊かな体験を発見できるよう支援しています。だからこそ、私たちは Google Play ストアで提供する体験の品質を、大切にしています。
そのために私たちは、ユーザー獲得からエンゲージメント、リテンション、再獲得に至るまで、ライフサイクル全体を通じて高品質のタイトルの促進と支援を行う新しい方法を常に開発しています。今後数か月の間に、Google Play と Google Play Console の機能強化を実施します。それにより、Google Play 上での体験全体にデベロッパーの皆さんの主要なイベント、コンテンツ、オファーが組み込まれるようになります。これらの変更は、より多くのユーザーにリーチし、より質の高いタイトルの発見を向上させ、デベロッパーの皆さんのプレゼンスを最適化することにより、最大の効果を得ることを目的としています。
これらのアップデートをナビゲートするために、私たちが特に重要だと思う考え方や方向性をいち早くお伝えし、ユーザーのライフサイクル全体でデベロッパーの皆さんのさらなる成長を後押しする新機能を発表します。ぜひご一読ください。
私たちはデベロッパーの皆さんのパートナーとして、皆さんのさらなる成功を後押しすることに重点を置いています。現在のアプリやゲームのエコシステムでは、ロイヤリティとリエンゲージメントがかつてないほど重要視されています。私たちは、デベロッパーの皆さんが私たちのサーフェスを活用して、新規ユーザーを見つけるだけでなく、ユーザーとエンゲージし、ユーザーを再獲得する機会をさらに増やすことに注力をしています。
一部のタイトルでは、LiveOps は Google Play でユーザーに直接、お得なオファー、ローンチ、イベントを提供するための重要なチャネルとなっています。LiveOps は、Rise of Kingdoms、Paramount+、MyFitnessPal などのタイトルが、ユーザーを発見し、リエンゲージメントを行い、マネタイズを促し、エキサイティングな新コンテンツを追加することに役立っています。LiveOps を使用しているデベロッパーは、使用していない類似タイトルと比較して、平均で収益が +3.6%、28DAU が +5.1% 増加しています。個々のイベントは、すでに Google Play 上で魅力的な成果を達成しています。
Google Play のデータ : LiveOps によってもたらされた実際に起きたさらなる成長
分析 : 直近で実施された 70 以上の LiveOps のうち 90% vs. 信頼区間 0.9 の対照群
このパフォーマンスを加速するため、今後数か月の間に、インパクトのある新しいプレースメントやフォーマットを作成し、Google Play でのコンテンツの利用方法を拡大します。コンテンツは、ホームページから、検索や発見、タイトルのリスト、ディープリンクを介したアプリへの直接アクセスなど、ユーザーの体験に深く組み込まれるようになります。
新しいコンテンツフォーマットにより、ユーザーが高品質なコンテンツを発見・再発見し、楽しむことができるようになります。最終的なデザインは異なる場合があります。
Google Play 上のコンテンツがもたらす機会を最大限に活用していただくために、Google Play Console 上で重要な変更を実施します。まず、LiveOps の名称を「プロモーション用コンテンツ」に変更します。これは、現在 Google Play に投稿できるコンテンツの幅と、今後追加される新しいコンテンツ タイプを反映したものです。また、フォーマットのガイドラインや優先順位のクォータも更新され、データの一括ダウンロードも可能になりました。すでにプロモーション用コンテンツを利用している何千ものタイトルの 1 つである場合、Google Play Console の受信トレイにメッセージが表示され、詳細を確認することができます。2023 年は、さらに多くのアプリとゲームへのアクセスを拡大する予定です。
これらの変更により、イベントを活用してアクティブな視聴者と収益を拡大する機会がさらに増えることになります。Google Play での可視性とプロモーションは、タイトルと個々のコンテンツの品質にも左右されます。そのため、更新されたコンテンツのガイドラインと推奨事項を必ずご確認ください。
モバイルアプリやゲームのライフサイクルのもう 1 つの重要な要素は、以前タイトルを試したことのあるユーザーを呼び戻すことです。モバイルのエコシステムが成熟し、デベロッパーの皆さんが長期的な投資を続ける中、この成長チャネルの重要性は増すばかりです。
そこで、離脱したユーザー向けのストアのカスタム掲載情報を導入します。これにより、アプリやゲームを試したことがあるがその後アンインストールした Google Play 上のユーザーに対して、異なるストーリーを伝えることができます。ストアの掲載情報は、YouTube でアプリの広告を表示するときのオーバーレイのような体験を提供するため、カスタマイズされたユーザーの再獲得のためのメッセージが、さまざまな Google サーフェスでユーザーの目に触れることが可能となります。
また、今後数ヶ月の間に、Google Play が高品質で素晴らしいタイトルのユーザーの再獲得をどのように支援できるかを検討していく予定です。離脱したユーザー向けのストアのカスタム掲載情報は、2022 年の年末に展開される予定です。このフォーム (英語) にご入力いただくことで、いち早く、この新機能を使用することへのご関心をお知らせいただくことができます。
私たちは、Google Play でユーザーのためにエキサイティングで新鮮なユーザー ジャーニーを作り上げることに注力しています。さまざまな要素がある中、品質評価は私たちのチームの判断とエディトリアル関連の決断において考慮される要因の 1 つとなっています。ユーザーは素晴らしい体験を期待しており、私たちはその期待に応えてくれるタイトルを支援することを目指しています。
まず、アプリ内品質を考慮します。私たちは、以下のようなさまざまな要素に着目しています。タイトルが洗練されたデザインで、ユーザーを長期的に惹きつけるコンテンツであるかどうか。オンボーディングエクスペリエンスが明確であるか、広告が適切に統合されているか。直感的に操作できるナビゲーション、コントロール、メニューアクセスがあるかどうか。サポートするすべてのフォームファクターにおいて、機能的な動作のガイドラインを満たしているかどうか。また、誰にとっても使いやすいアプリやゲームになっているかどうか、などの要素を見ています。
技術的な品質も重要な検討事項です。技術的な品質は、ユーザーやデバイスによって大きく異なる可能性があるため、よりユーザーに焦点を当てたクラッシュと ANR の新しい指標を Android vitals に導入します。これらの指標は、デベロッパーの皆さんが、Google Play ストア上で受ける対応に影響を与えるようになります。特定のデバイスで劣悪な体験を提供する可能性があるタイトルからユーザーを遠ざけ、ユーザーが、より適切なタイトルに誘導されるようになります。また、一部のアプリやゲームでは、ストアの掲載情報に警告が表示される場合があります。
ユーザーが利用しているモバイル端末においてユーザーが認識したクラッシュ レートまたは ユーザーが認識した ANR 率 が 8% を超えるタイトルがある場合、2022 年 11 月 30 日以降ストアの掲載情報上に警告が表示される場合があります。なお、最終的なデザインやテキストは変更される可能性があります。
このような警告が Google Play で表示される前に、アプリに該当リスクがある場合は Android vitals で警告しますので、改善策を講じることが可能です。詳しくは、アプリの品質に関するブログポストをご覧ください。
最後に品質に関する話題に関連して、トップ ランキングを改善するために、レーティングの最低基準を 3.0 に設定することになりました。私たちはまず、2023 年 2 月に世界中の、すべてのフォームファクターにおいて、無料のトップ ランキングへのレーティング変更を開始する予定です。2023 年後半には、この変更を有料と売上のトップ ランキングにも導入する予定です。
Google Play Console には、評価のトリアージ、ユーザーが書き込んでいる上位の問題の深堀り、レビューへの直接の返信するためのツールが用意されています。詳しくはこちらをご覧ください。
Google Play をデベロッパーの皆さんにとって、より価値のあるプラットフォームにしていくために、私たちのサーフェスがデベロッパーの皆さんの成長に与えるポジティブな影響を明確化できるようにしたいと考えています。そこで、Google Play ストアのパフォーマンス レポートを更新し、ユーザーが Google Play で皆さんのタイトルをどのように発見しているかという情報を、よりよく反映するようにしました。これには、より多くの Google Play 以外のエクスペリエンスからのデータ、Google Play でのユーザーのオーガニックな行動や、有料とダイレクトトラフィックの区別、主要なディスカバリー ジャーニー(たとえば、「パズルゲーム」などのカテゴリーを検索)の探索トラフィックへの組み込みが含まれます。このアップデートが公開される際には、Google Play Console からメッセージでお知らせします。
これらの変更と、現在開発中のその他のエキサイティングな機能は、すべて連動しています。ユーザー体験の質の向上、サービスの進化、心が躍るようなイベントや魅力的なコンテンツに投資しているデベロッパーの方々にとって、Google Play は、皆さんのさらなる成長と成功を強力に後押しするプラットフォームであり、パートナーであり続けます。
この記事は Diego Zavala, Christiaan Brand, Ali Naddaf, Ken Buchanan による Android Developers Blog/Android Developers - Medium の記事 " Bringing passkeys to Android & Chrome " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
2022 年 10 月 12日より、Google は Android と Chrome の両方でパスキーのサポートを開始しました。
パスキーはフィッシングによって侵害される可能性があるパスワードなどの認証要素に代わるもので、安全性が大幅に向上します。パスキーは再利用できず、サーバーの侵害によって漏洩することもないため、ユーザーはフィッシング攻撃から保護されます。また、業界標準 (英語) に基づいて作られており、オペレーティング システムやブラウザのエコシステムに依存せずに動作し、ウェブサイトでもアプリでも利用できます。
パスキーはパスワードの自動入力という既存の仕組みに基づいているので、おなじみの UX パターンに従います。エンドユーザーは、保存してあるパスワードを使うときと同じ操作でパスキーを利用できます。つまり、指紋などの既存のデバイスの画面ロック解除キーを提示するだけです。ユーザーのスマートフォンやコンピュータに保存されたパスキーは、クラウドを通してバックアップと同期されるので、デバイスを紛失してもロックアウトされることはありません。さらに、スマートフォンに保存されているパスキーを使って、そばにある別のデバイスからアプリやウェブサイトにログインすることもできます。
10 月 12 日のサポート開始により、パスキーに関する作業が大きな節目を迎え、次の 2 つの主な機能が実現できるようになったことをお知らせします。
さっそく試してみたいデベロッパーの皆さんは、Google Play 開発者サービスのベータ版 (英語) に登録し、Chrome Beta を使ってみてください。どちらの機能も、今年中に安定版チャンネルで一般公開版として利用できるようになる予定です。
2022 年の次のマイルストーンとして、ネイティブ Android アプリ向けの API を提供する予定です。ウェブ API を使って作成したパスキーは、同じドメインを使うアプリでシームレスに利用できます。その逆も同様です。ネイティブ API を使うと、パスキーと保存したパスワードのどちらかを選ぶ仕組みを統一的に扱うアプリを作ることができます。パスワードとパスキーの両方でおなじみのシームレスな UX を使えるので、ユーザーやデベロッパーは徐々にパスキーに移行しやすくなります。
エンドユーザーは、わずか 2 ステップでパスキーを作成できます。(1)パスキーのアカウント情報を確認し、(2)プロンプトに従って指紋や顔などの画面ロック解除キーを提示します。
ログイン操作も同じように簡単です。(1)ログインに使うアカウントを選択し、(2)プロンプトに従って指紋や顔などの画面ロック解除キーを提示します。
スマートフォンに保存したパスキーは、そばにあるデバイスからログインする際に使うこともできます。たとえば、Android ユーザーは Mac の Safari からパスキー対応のウェブサイトにログインできます。同じように、パスキーは Chrome でもサポートされているので、たとえば Windows の Chrome ユーザーが iOS デバイスに保存されているパスキーを使ってログインすることもできます。
パスキーは業界標準に基づいて作られているので、Windows、macOS、iOS、ChromeOS など、プラットフォームやブラウザが違っても、同じユーザー エクスペリエンスを提供できます。
私たちは、Apple や Microsoft などの同じ業界の企業、FIDO Alliance (英語) のメンバー、W3C (英語) と協力し、何年もかけて (英語) 安全な認証標準の検討を進めています。W3C WebAuthn (英語) や FIDO 標準は、制定時よりサポートしています。
今回重要な節目ではありますが、これでこの取り組みが終わるわけではありません。Google は、パスワードや新たに導入されたパスキーを保存する場所をユーザーが自由に選べる世界に向けて、これからも注力し続けます。来年には、Android に変更を加え、サードパーティのパスワードマネージャーがパスキーをサポートできるようにする予定なので、今後の最新情報にご注目ください。