Wear OS で初めてのタイルを作成する

1. はじめに

3 つのタイル: フィットネス、メッセージ、カレンダー。

Wear OS タイルを使うと、必要な情報やアクションに容易にアクセスできます。ウォッチフェイスをスワイプするだけで、最新の天気予報を確認したり、タイマーを開始したりすることが可能です。

タイルは、独自のアプリコンテナで実行されるのではなく、システム UI の一部として実行されます。Service を使用して、タイルのレイアウトとコンテンツを記述します。これにより、システム UI が必要に応じてタイルのレンダリングを行います。

演習内容

656045bed9c45083.png

メッセージ アプリ用に、最近の会話を表示するタイルを作成します。このサーフェスから、ユーザーは次の 3 つの一般的なタスクに移動できます。

  • 会話を開く
  • 新しいメッセージを作成する

学習内容

この Codelab では、以下を行う方法を含め、独自の Wear OS タイルを作成する方法を学びます。

  • TileService を作成する
  • デバイスでタイルをテストする
  • Android Studio でタイルの UI をプレビューする
  • タイルの UI を開発する
  • 画像を追加する
  • インタラクションを処理する

前提条件

  • Kotlin に関する基礎知識

2. 設定方法

このステップでは、環境を設定してスターター プロジェクトをダウンロードします。

必要なもの

Wear OS の使用に慣れていない場合は、開始する前にこちらのクイックガイドをお読みになることをおすすめします。Wear OS エミュレータのセットアップ手順とシステムの操作方法が記載されています。

コードをダウンロードする

git がインストールされている場合は、以下のコマンドをそのまま実行してこのリポジトリからコードのクローンを作成できます。

git clone https://github.com/android/codelab-wear-tiles.git
cd codelab-wear-tiles

git がない場合は、次のボタンをクリックして、この Codelab のすべてのコードをダウンロードできます。

Android Studio でプロジェクトを開く

[Welcome to Android Studio] ウィンドウで c01826594f360d94.png [Open an Existing Project] を選択するか、[File] > [Open] を開いて [Download Location] フォルダを選択します。

3. 基本的なタイルを作成する

タイルのエントリ ポイントはタイルサービスです。このステップでは、タイルサービスを登録し、タイルのレイアウトを定義します。

HelloWorldTileService

TileService を実装するクラスは、次の 2 つのメソッドを指定する必要があります。

  • onTileResourcesRequest(requestParams: ResourcesRequest): ListenableFuture<Resources>
  • onTileRequest(requestParams: TileRequest): ListenableFuture<Tile>

最初のメソッドは Resources オブジェクトを返します。これは文字列 ID をタイルで使用する画像リソースにマッピングします。

2 つ目はレイアウトの説明を含むタイルの説明を返します。ここで、タイルのレイアウトと、データがタイルにどのようにバインドされるかを定義します。

start モジュールから HelloWorldTileService.kt を開きますこれから行う変更はすべて、このモジュールに反映されます。また、この Codelab の結果を確認したい場合は、finished モジュールが用意されています。

HelloWorldTileService は、Horologist Tiles ライブラリの Kotlin コルーチンに適したラッパーである SuspendingTileService を拡張します。Horologist は、Google が開発したライブラリのグループであり、デベロッパーが通常必要としている機能であるものの、Jetpack ではまだ利用できない機能を Wear OS デベロッパー向けに補完することを目的としています。

SuspendingTileService には、TileService の関数と同等のコルーチンである suspend 関数が 2 つあります。

  • suspend resourcesRequest(requestParams: ResourcesRequest): Resources
  • suspend tileRequest(requestParams: TileRequest): Tile

コルーチンについて詳しくは、Android での Kotlin コルーチンのドキュメントをご覧ください。

HelloWorldTileServiceまだ完成していません。マニフェストにサービスを登録する必要があります。また、tileLayout の実装を提供する必要もあります。

タイルサービスを登録する

タイルサービスがマニフェストに登録されると、ユーザーが追加できるタイルのリストに表示されます。

<application> 要素内に <service> を追加します。

start/src/main/AndroidManifest.xml

<service
    android:name="com.example.wear.tiles.hello.HelloWorldTileService"
    android:icon="@drawable/ic_waving_hand_24"
    android:label="@string/hello_tile_label"
    android:description="@string/hello_tile_description"
    android:exported="true"
    android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
    
    <intent-filter>
        <action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
    </intent-filter>

    <!-- The tile preview shown when configuring tiles on your phone -->
    <meta-data
        android:name="androidx.wear.tiles.PREVIEW"
        android:resource="@drawable/tile_hello" />
</service>

タイルが初めて読み込まれたとき、またはタイルの読み込み中にエラーが発生した場合、アイコンとラベルが(プレースホルダとして)使用されます。最後のメタデータは、ユーザーがタイルを追加するときにカルーセルに表示されるプレビュー画像を定義します。

タイルのレイアウトを定義する

HelloWorldTileService には、本文が TODO()tileLayout という関数があります。これを、タイルのレイアウトを定義する実装に置き換えてみましょう。

start/src/main/java/com/example/wear/tiles/hello/HelloWorldTileService.kt

fun tileLayout(
    context: Context,
    deviceConfiguration: DeviceParametersBuilders.DeviceParameters,
    message: String,
) =
    materialScope(
        context = context,
        deviceConfiguration = deviceConfiguration,
        allowDynamicTheme = false,
    ) {
        primaryLayout(mainSlot = { text(message.layoutString) })
    }

これで、初めての Wear OS タイルを作成できました。このタイルをインストールして、どのように表示されるかを確認してみましょう。

4. デバイスでタイルをテストする

実行構成のプルダウンで start モジュールを選択した状態で、デバイスまたはエミュレータにアプリ(start モジュール)をインストールすることも、ユーザーと同様にタイルを手動でインストールすることもできます。

ただし、Android Studio には、この操作を行うためのショートカットがあります。ガターに表示されている「サービス実行」アイコン(▷)をタップし、[Run 'HelloWorldTileService'] を選択すると、接続されたデバイスにタイルがインストールされ、起動されます。

ded9f9355abd02f3.png

[Run ‘HelloWorldTileService'] を選択して、接続されたデバイスでタイルをビルドして実行します。次のスクリーンショットのように表示されます。

693c130912097be6.png

ディスプレイの上部に表示される「手を振る」アイコンは、システムによって提供されます。これを変更するには、マニフェストでタイルの <service> 要素の android:icon プロパティを変更します。

このプロセスでは、今後使用できるように「HelloWorldTileService」の「実行構成」も作成されます。

b3335148771abbeb.png

5. プレビュー関数を追加する

Android Studio でタイル UI をプレビューできます。これにより、UI 開発時のフィードバック ループが短縮され、開発速度が向上します。

HelloWorldTileService.kt ファイルの最後に HelloWorldTileService のタイル プレビューを追加します。

start/src/main/java/com/example/wear/tiles/hello/HelloWorldTileService.kt

@Preview(device = WearDevices.SMALL_ROUND, name = "Small Round")
@Preview(device = WearDevices.LARGE_ROUND, name = "Large Round")
internal fun helloLayoutPreview(context: Context): TilePreviewData {
    return TilePreviewData {
        TilePreviewHelper.singleTimelineEntryTileBuilder(
            helloLayout(context, it.deviceConfiguration, "Hello, preview tile!")
        )
            .build()
    }
}

[Split] エディタモードを使用すると、タイルのプレビューが表示されます。

Android Studio の分割画面ビュー。左側にプレビュー コード、右側にタイルの画像が表示されている。

@Composable アノテーションは使用されません。タイルはコンポーズ可能な関数と同じプレビュー UI を使用しますが、Compose は利用せず、コンポーズ可能ではありません。

6. メッセージ タイルを作成する

cf18db0f604b1999.png

これから作成するメッセージ タイルは、より実際のタイルに近いものです。HelloWorld の例とは異なり、Material 3 Expressive のコンポーネントを示し、画像を表示し、アプリを開くための操作を処理します。

MessagingTileService

MessagingTileService は、前述の SuspendingTileService クラスを拡張します。

7. UI コンポーネントを追加する

ProtoLayout ライブラリにはビルド済みのコンポーネントとレイアウトが用意されているため、Wear OS 向けの最新の Material 3 Expressive デザインを採用したタイルを作成できます。

Tiles Material の依存関係を build.gradle ファイルに追加します。

start/build.gradle

implementation "androidx.wear.protolayout:protolayout-material3:$protoLayoutVersion"

レイアウト コードを、materialScope() 関数の本文として tileLayout() 関数に追加します。これにより、2 つの行(それぞれが 2 つのボタンを持つ)と 1 つのエッジボタンで構成されるレイアウトが作成されます。

TODO() // Add primaryLayout()」という行を見つけて、次のコードに置き換えます。

start/src/main/java/com/example/wear/tiles/messaging/tile/Layout.kt

primaryLayout(
    mainSlot = {
        // This layout code assumes "contacts" contains at least 4 elements, for sample code
        // that can handle an arbitrary number of contacts, and also shows different numbers
        // of contacts based on the physical screen size, see
        // <https://github.com/android/wear-os-samples/tree/main/WearTilesKotlin>.
        Column.Builder()
            .apply {
                setWidth(expand())
                setHeight(expand())
                addContent(
                    buttonGroup {
                        buttonGroupItem { contactButton(contacts[0]) }
                        buttonGroupItem { contactButton(contacts[1]) }
                    }
                )
                addContent(DEFAULT_SPACER_BETWEEN_BUTTON_GROUPS)
                addContent(
                    buttonGroup {
                        buttonGroupItem { contactButton(contacts[2]) }
                        buttonGroupItem { contactButton(contacts[3]) }
                    }
                )
            }
            .build()
    },
    bottomSlot = {
        textEdgeButton(
            onClick = clickable(), // TODO: Launch new conversation activity
            labelContent = { text("New".layoutString) },
        )
    },
)

同じファイルの contactButton() 関数は、個々の連絡先ボタンを作成します。連絡先に画像が関連付けられている場合は、その画像がボタンに表示されます。それ以外の場合は、連絡先のイニシャルが使用されます。

この時点では、全体的なレイアウトは正しいものの、画像が表示されていません。

809bdb9d1213c376.png

タイルをデバイスにデプロイしても、同じ結果となります。

4671bb2eafdcc528.png

次のステップでは、画像の欠落を修正します。

8. 画像を追加する

大まかに言うと、タイルはレイアウト(文字列 ID でリソースを参照)とリソース自体(画像など)の 2 つの要素で構成されます。

現時点では、コードはレイアウトを提供していますが、リソース自体は提供していません。プレビューを修正するには、画像の「リソース」を指定する必要があります。そのためには、「TODO: Add onTileResourceRequest」を検索し、次のコードを名前付き引数として TilePreviewData() に追加します。

start/src/main/java/com/example/wear/tiles/messaging/tile/Layout.kt

// Additional named argument to TilePreviewData
onTileResourceRequest = { resourcesRequest ->
    Resources.Builder()
        .setVersion(resourcesRequest.version)
        .apply {
            contacts.forEach {
                if (it.avatarSource is AvatarSource.Resource) {
                    addIdToImageMapping(
                        it.imageResourceId(),
                        it.avatarSource.resourceId
                    )
                }
            }
        }
        .build()
}

画像がプレビューに表示されるはずです。

e77d746268f293f2.png

ただし、タイルがデバイスにデプロイされている場合、画像は表示されません。この問題を解決するには、Service.ktresourcesRequest() 関数を次のコードに置き換えます。

start/src/main/java/com/example/wear/tiles/messaging/tile/Service.kt

override suspend fun resourcesRequest(
    requestParams: ResourcesRequest
): Resources {
    // resourceIds is a list of the ids we need to provide images for. If we're passed an empty
    // list, set resourceIds to all resources.
    val resourceIds =
        requestParams.resourceIds.ifEmpty {
            contacts.map { it.imageResourceId() }
        }

    // resourceMap maps (tile) resource ids to (Android) resource ids.
    val resourceMap =
        contacts
            .mapNotNull {
                when (it.avatarSource) {
                    is AvatarSource.Resource ->
                        it.imageResourceId() to
                            it.avatarSource.resourceId
                    else -> null
                }
            }
            .toMap()
            .filterKeys {
                it in resourceIds
            } // filter to only the resources we need

    // Add images in the resourceMap to the Resources object, and return the result.
    return Resources.Builder()
        .setVersion(requestParams.version)
        .apply {
            resourceMap.forEach { (id, imageResource) ->
                addIdToImageMapping(id, imageResource)
            }
        }
        .build()
}

タイルがデバイスにデプロイされたときに画像も表示されるようになりました。

cf18db0f604b1999.png

次のステップでは、各要素のクリックを処理します。

9. インタラクションを処理する

タイルでできる便利なこととして、クリティカル ユーザー ジャーニーのショートカットの提供が挙げられます。単にアプリを起動するアプリ ランチャーとは異なり、アプリ内の特定の画面に状況依存のショートカットを表示するスペースがあります。

これまで、チップと各ボタンには引数なしの clickable() によって提供されるダミー アクションを使用してきました。これはインタラクティブではないプレビューには適していますが、要素にアクションを追加する方法を見てみましょう。

LaunchAction

LaunchAction はアクティビティの起動に使用できます。Layout を変更して、[New] ボタンをタップすると「新しい会話」のユーザー ジャーニーが開始されるようにしましょう。

TODO: Launch new conversation activity」という行を見つけて、clickable() を次のコードに置き換えます。

start/src/main/java/com/example/wear/tiles/messaging/tile/Layout.kt

clickable(
    id = "new_button",
    action =
        launchAction(
            ComponentName(
                "com.example.wear.tiles",
                "com.example.wear.tiles.messaging.MainActivity",
            ),
            mapOf(
                MainActivity.EXTRA_JOURNEY to
                    ActionBuilders.stringExtra(
                        MainActivity.EXTRA_JOURNEY_NEW
                    )
            ),
        ),
)

タイルを再デプロイします。これで、[New] をタップすると MainActivity が起動し、「新しい会話」のユーザー ジャーニーが開始されるようになりました。

a08c28b4a142fb8f.png

同様に、連絡先ボタンをタップすると特定のユーザーとの会話が開始されるように Layout を変更します。

Launch open conversation activity」という行を見つけて、clickable() を次のコードに置き換えます。

start/src/main/java/com/example/wear/tiles/messaging/tile/Layout.kt

clickable(
    id = contact.id.toString(),
    action =
        launchAction(
            ComponentName(
                "com.example.wear.tiles",
                "com.example.wear.tiles.messaging.MainActivity",
            ),
            mapOf(
                MainActivity.EXTRA_JOURNEY to
                    ActionBuilders.stringExtra(
                        MainActivity
                            .EXTRA_JOURNEY_CONVERSATION
                    ),
                MainActivity.EXTRA_CONVERSATION_CONTACT to
                    ActionBuilders.stringExtra(
                        contact.name
                    ),
            ),
        ),
)

タイルを再デプロイします。これで、連絡先をタップすると、その連絡先との会話が開始されるようになりました。

b684a1ced0b226f9.png

10. 完了

お疲れさまでした。Wear OS のタイルの作成方法を学習しました。

次のステップ

GitHub の Golden Tiles の実装Wear OS タイルのガイド設計ガイドラインで、さらに詳しい情報をご確認ください。