Glance でウィジェットを作成する

1. 始める前に

この Codelab では、SociaLite のアプリ ウィジェットの作成プロセスを説明します。まず、シンプルな Glance ウィジェットを作成し、これを SociaLite とホーム画面に追加します。次に、Glance のコンポーネントとテーマを使用してウィジェットにゼロ状態を追加します。その後、ユーザーがウィジェットからお気に入りの連絡先を選択できるようにします。最後に、アプリからウィジェットを更新する方法を説明します。

前提条件

  • Kotlin の基礎知識があること。
  • Codelab Android Studio をセットアップするを完了していること、または Android Studio の使用方法と Android 15 のエミュレータまたは実機でアプリをテストする方法に精通していること。
  • Hilt の基礎知識があること。
  • Compose の基礎知識があること。Glance では Jetpack Compose のコンポーズ可能な関数は使用しませんが、フレームワークとコーディング スタイルは流用します。

この Codelab で学ぶこと

  • ウィジェットをサポートするようにアプリを構成する方法。
  • Glance コンポーネントを使用してレスポンシブ レイアウトを構築する方法。
  • GlanceTheme を使用してユーザーのホーム画面でダイナミック カラーをサポートする方法。
  • ウィジェットでユーザー操作を処理する方法。
  • アプリからウィジェットを更新する方法。

必要なもの

  • Android Studio の最新バージョン
  • Android 12 以降を実行できるテストデバイスまたはエミュレータ。
  • Android 12 以降の SDK。

SociaLite ウィジェットが表示された Android のホーム画面

2. セットアップする

スターター コードを取得する

  1. 「Android 15 でのエッジ ツー エッジの適用の処理」または「予測型『戻る』アニメーションを追加する」の Codelab を完了している場合は、すでにスターター コードを取得しているため、ウィジェットを追加するのセクションに進んでください。
  2. GitHub からスターター コードをダウンロードします。

または、リポジトリのクローンを作成して codelab_improve_android_experience_2024 ブランチをチェックアウトすることもできます。

 git clone git@github.com:android/socialite.git
 cd socialite
 git checkout codelab_improve_android_experience_2024
  1. Android Studio で SociaLite を開き、Android 15 を搭載したデバイスまたはエミュレータでアプリを実行します。次のような画面が表示されます。

fb043d54dd01b3e5.png

ジェスチャー ナビゲーションを追加した SociaLite

3. ウィジェットを追加する

ウィジェットとは

ウィジェットとは、他の Android アプリ内に埋め込み可能なアプリの一部分のことです。最も一般的な例は、ユーザーのホーム画面です。

アプリにウィジェットを追加することで、ユーザーは一般的なタスクをすばやく開始したり、情報をひと目で確認したりできるほか、アプリが提供するコンテンツでデバイスをカスタマイズできるようになります。

Glance とは

Jetpack Glance とは、Kotlin で Compose のような API を使用してウィジェットを作成するためのライブラリです。再コンポーズ、Kotlin で記述された宣言型 UI コード、独自性の強いコンポーネントなど、Compose と同様のメリットを備えています。Glance を使用することで、ウィジェットで XML リモートビューを使用する必要がほとんどなくなります。

ウィジェットを作成する

Android のウィジェットは、AndroidManifest<receiver> 要素として宣言されます。このレシーバはエクスポートする必要があり、android.appwidget.action.APPWIDGET_UPDATE アクション インテントを処理し、android.appwidget.provider というメタデータ要素を通じてアプリ ウィジェットの設定ファイルを提供する必要があります。

SociaLite にウィジェットを追加する

SociaLite ウィジェットが表示された Android のホーム画面

ユーザーがお気に入りの連絡先を表示し、その連絡先からの未読メッセージがあるかどうかを確認できるウィジェットを SociaLite に追加します。未読メッセージがある場合、ウィジェットをタップしてその相手とのチャットに移動できるようにします。また、Glance のコンポーネントとテーマ設定を使用することで、レスポンシブ デザインとダイナミック カラーを導入してウィジェットの表示を最適化できます。

まず SociaLite に静的な「Hello World」ウィジェットを追加し、後でウィジェットの機能を拡張します。

手順は次のとおりです。

  1. アプリに Glance の依存関係を追加します。
  2. GlanceAppWidget の実装を作成します。
  3. GlanceAppWidgetReceiver を作成します。
  4. アプリ ウィジェットの情報が記述された XML ファイルを使用してウィジェットを構成します。
  5. レシーバとアプリ ウィジェットの情報を AndroidManifest.xml ファイルに追加します。

プロジェクトに Glance を追加する

スターター コードにより、Glance の各バージョンとライブラリ座標が SociaLite のバージョン カタログ libs.versions.toml に追加されています。

libs.versions.toml

[versions]
//..

glance = "1.1.1"

[libraries]
glance-appwidget = { group = "androidx.glance", name = "glance-appwidget", version.ref = "glance" }
glance-material = { group = "androidx.glance", name = "glance-material3", version.ref = "glance" }

また、Glance の依存関係は SociaLite の app/build.gradle.kts ファイルに含まれています。

build.gradle.kts
dependencies {
...
implementation(libs.glance.appwidget)
implementation(libs.glance.material)

...
}
  • これらのファイルを変更した場合は、プロジェクトを同期して Glance ライブラリをダウンロードします。

GlanceAppWidgetGlanceAppWidgetReceiver を作成する

Android はブロードキャスト レシーバを使用して、ウィジェットが追加または削除されたことや、ウィジェットを更新する必要があることを SociaLite に通知します。Glance は、AppWidgetProvider を拡張する抽象レシーバクラス GlanceAppWidgetReceiver を提供します。

GlanceAppWidgetReceiver の実装は GlanceAppWidget のインスタンスも提供します。このクラスは Glance のコンポーズ可能な関数をリモートビューにレンダリングします。

スターター コードには、GlanceAppWidget を拡張する SocialiteAppWidget と、GlanceAppWidgetReceiver を拡張する SocialiteAppWidgetReceiver という 2 つのクラスが含まれています。

まず、次の手順を行います。

  1. app/src/main/java/com/google/android/samples/socialite/ にある widget パッケージに移動します。
  2. SociaLiteAppWidget クラスを開きます。このクラスは provideGlance メソッドをオーバーライドします。
  3. TODOprovideContent の呼び出しに置き換えてから、ウィジェットのコンポーズ可能な関数をパラメータとして渡します。現時点では、ウィジェットには Hello World というメッセージのみが表示されますが、この Codelab で後ほどさらに機能を追加します。
package com.google.android.samples.socialite.widget

import android.content.Context
import androidx.glance.GlanceId
import androidx.glance.GlanceTheme
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.provideContent
import androidx.glance.text.Text

class SociaLiteAppWidget : GlanceAppWidget() {
   override suspend fun provideGlance(context: Context, id: GlanceId) {
       provideContent {
           GlanceTheme {
               Text("Hello World")
           }
       }
   }
}
  1. widget パッケージで SociaLiteAppWidgetReceiver クラスを開きます。現時点では、レシーバは SociaLiteWidget のインスタンスを提供しますが、次のセクションでさらに多くの機能を追加します。
  2. TODOSociaLiteAppWidget() コンストラクタに置き換えます。
package com.google.android.samples.socialite.widget

import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver

class SociaLiteAppWidgetReceiver : GlanceAppWidgetReceiver() {
   override val glanceAppWidget: GlanceAppWidget = SociaLiteAppWidget()
}

これで、ウィジェットを表示し、ユーザーがウィジェットをホーム画面に追加できるように Android を構成する準備が整いました。

アプリ ウィジェットのプロバイダ情報を追加する

  1. **res/xml** を右クリックし、[New] > [XML resource file] を選択します。
  2. ファイル名として socialite_widget_info、ルート要素として appwidget-provider を入力し、[OK] をクリックします。このファイルには、最初にウィジェットを表示するために AppWidgetHost が使用する appwidget のメタデータが含まれています。
  3. socialite_widget_info.xml ファイルに次のコードを追加します。
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
   android:resizeMode="horizontal|vertical"
   android:updatePeriodMillis="3600000"
   android:minHeight="128dp"
   android:minWidth="128dp"

   android:minResizeHeight="128dp"
   android:minResizeWidth="128dp"
   android:configure="com.google.android.samples.socialite.widget.SociaLiteAppWidgetConfigActivity"
   android:widgetFeatures="configuration_optional|reconfigurable"
   android:previewImage="@drawable/widget_preview"
   android:maxResizeHeight="512dp"
   android:maxResizeWidth="512dp"
   android:targetCellWidth="2"
   android:targetCellHeight="2"
   android:initialLayout="@layout/glance_default_loading_layout">
</appwidget-provider>

次の表に、このコード内の各属性の概要と、それぞれの説明をまとめています。

属性名

説明

resizeMode

ウィジェットのサイズを縦方向および横方向に変更できます。

targetCellWidth, targetCellHeight

ホーム画面にウィジェットを追加する際のデフォルト サイズを指定します。

updatePeriodMillis

ホストがウィジェットの更新を決定するタイミングを制御します。ウィジェットの実行中に表示すべき新しい情報が発生した場合、アプリはいつでもウィジェットを更新できます。

minResizeHeight,minResizeWidth

ウィジェットのサイズを変更する際の最小サイズを設定します。

minHeight,minWidth

ウィジェットをホーム画面に追加する際の最小デフォルト サイズを設定します。

initialLayout

Glance がコンポーズ可能な関数をレンダリングする際に表示される最初のレイアウトを指定します。

previewImage

ウィジェット選択ツールに表示されるウィジェットの静止画像を指定します。

widgetFeatures

ウィジェットがサポートするさまざまな機能を示します。これらはウィジェット ホストにヒントを提供するものであり、ウィジェットの動作を変更することはありません。

この Codelab では、フラグはホストに対して、ウィジェットをホーム画面に追加する前に構成する必要がないこと、ウィジェットを追加した後で構成できることを伝えます。

configure

構成アクティビティ クラスの名前。これは後でウィジェットを構成するアクティビティです。

API 31 以降の機能を含むすべての使用可能な属性については、AppWidgetProviderInfo をご覧ください。

AndroidManifest を更新してウィジェットをテストする

これで、AndroidManifest.xml ファイルを更新してウィジェットをテストする準備が整いました。次に、ファイル内で receiver 要素を application 要素の子として定義します。このレシーバは APPWIDGET_UPDATE インテントを処理し、Android ランチャーに appwidget メタデータを提供します。

まず、次の手順を行います。

  1. エクスポートする SociaLiteAppWidgetReceiverreceiver 要素を作成します。次のコードをコピーして AndroidManifest.xml ファイルの application 要素の後ろに貼り付けます。
<receiver
   android:name=".widget.SociaLiteAppWidgetReceiver"
   android:exported="true"
   android:label="Favorite Contact">

   <intent-filter>
       <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
   </intent-filter>

   <meta-data
       android:name="android.appwidget.provider"
       android:resource="@xml/socialite_widget_info" />
</receiver>
  1. アプリをコンパイルして実行します。
  2. アプリが起動したら、ホーム画面にウィジェットを追加します。たとえば Google Pixel では、背景を長押しし、[ウィジェット] > [SociaLite] を選択すると、ホーム画面にウィジェットを追加できます。

作業中のウィジェットが表示された Android のホーム画面。ウィジェットは透明で、次のメッセージが表示されています。

ウィジェットには「Hello World」というメッセージが表示されており、背景は透明です。このままでは見栄えが悪く、機能性にも劣るため、次のセクションでより複雑なレイアウトを追加し、マテリアル デザインのカラーを使用してウィジェットの外観を改善します。

4. デザインを改善する

このウィジェットは静的で、優れたウィジェットに備わっている多くの機能が欠けています。優れたウィジェットには次のような特長があります。

  • 常に新しいコンテンツを簡潔に表示し、機能のシンプルさを維持する。
  • サイズ変更可能なレイアウトにより、なるべく隙間ができないようにする。
  • アプリ ウィジェット ホストの背景色に合わせる。

優れたウィジェットの特長について詳しくは、ウィジェットをご覧ください。

Scaffold を追加する

次に、Glance の Scaffold コンポーネントで表示するようウィジェットを更新します。

Scaffold は Glance ライブラリによって提供されます。TitleBar でウィジェットの UI を表示するためのシンプルなスロット API です。背景色を GlanceTheme.colors.widgetBackground に設定し、パディングを適用します。これが最上位のコンポーネントになります。

まず、次の手順を行います。

  1. SociaLiteAppWidget の実装を次のコードに置き換えます。
package com.google.android.samples.socialite.widget

import android.content.Context
import androidx.compose.runtime.Composable
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.ImageProvider
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.components.Scaffold
import androidx.glance.appwidget.components.TitleBar
import androidx.glance.appwidget.provideContent
import androidx.glance.layout.fillMaxSize
import androidx.glance.text.Text
import com.google.android.samples.socialite.R

class SociaLiteAppWidget : GlanceAppWidget() {
   override suspend fun provideGlance(context: Context, id: GlanceId) {
       provideContent {
           GlanceTheme() {
               Content()
           }
       }
   }

   @Composable
   private fun Content() {
       Scaffold(titleBar = {TitleBar(startIcon = ImageProvider(R.drawable.ic_launcher_monochrome), title = "SociaLite")},
           modifier = GlanceModifier.fillMaxSize()) {
           Text("Hello World")
       }
   }
}
  1. 更新を確認するには、アプリを再実行してから、ウィジェットの新しいコピーをホーム画面に追加します。

作業中のウィジェットが表示された Android のホーム画面。ウィジェットには不透明な背景とタイトルが表示されています。

ウィジェットは外部ホストによって表示されるリモートビューであることに注意してください。後ほど、アプリ内でウィジェットを自動的に更新する機能を追加します。それまでは、ウィジェット選択ツールからウィジェットを追加してコードの変更を確認する必要があります。

ご覧のようにかなり改善されましたが、他のウィジェットと比べて色に違和感があります。ホーム画面では、ウィジェットの色はユーザーのテーマ設定に基づいて設定されることが期待されるためです。ダイナミック カラートークンを使用することで、ウィジェットのテーマをデバイスの壁紙とテーマに適合させることができます。

ダイナミック カラーを追加する

widgetBackground のカラートークンを Scaffold の背景に追加し、onSurface のカラートークンを TitleBar のテキストおよび Text コンポーネントに追加します。テキストのスタイルを更新するには、TextStyle Glance クラスをインポートする必要があります。Scaffold の背景を更新するには、ScaffoldbackgroundColor プロパティを GlanceTheme.colors.widgetBackground に設定します。

まず、次の手順を行います。

  1. 新しいインポートを SociaLiteAppWidget.kt ファイルに含めます。
//Add to the imports section of your Kotlin code.
import androidx.glance.text.TextStyle
  1. Content コンポーズ可能な関数を更新して widgetBackground を追加します。
Scaffold(
   titleBar = {
       TitleBar(
           textColor = GlanceTheme.colors.onSurface,
           startIcon = ImageProvider(R.drawable.ic_launcher_monochrome),
           title = "SociaLite",
       )
   },
   backgroundColor = GlanceTheme.colors.widgetBackground,
   modifier = GlanceModifier.fillMaxSize(),
) {
   Text(text = "Hello World", style = TextStyle(color = GlanceTheme.colors.onSurface))
}
  1. 更新を確認するには、アプリを再実行してから、ウィジェットの新しいコピーをホーム画面に追加します。

これで、ウィジェットの色がホーム画面の他のウィジェットのテーマに合わせて変更されるようになります。ユーザーが背景を変更したり、ダークモードに設定したりすると、色が自動的に更新されます。カラフルな背景の場合、ウィジェットの色は配置されている背景のセクションに合わせて変更されます。

Hello World ウィジェットがライトモードで表示されている Android のホーム画面ライトモードのホーム画面

Hello World ウィジェットがダークモードで表示されている Android のホーム画面ダークモードのホーム画面

ゼロ状態を追加する

次に、状態とウィジェットの構成について考える必要があります。ホーム画面にウィジェットが追加されたときに構成を求める場合、通常はゼロ状態を表示することをおすすめします。ゼロ状態では、ユーザーにウィジェットを構成するよう促します。ウィジェットに構成アクティビティを追加し、ゼロ状態から構成アクティビティへのリンクを設定します。

この Codelab では、ウィジェットの構成状態の保存、アクセス、変更を行うクラスを指定します。ウィジェットの UI を更新してその状態を表示するコードを追加し、ユーザーのタップ操作を処理するラムダ アクションを作成します。

ウィジェット モデルを確認する

パッケージ com.google.android.samples.socialite.widget.model のクラスを確認しておきましょう。

このパッケージには、WidgetModel クラス、WidgetModelDao クラス、WidgetModelRepository クラスが含まれています。これらのクラスは Codelab のスターター コードにすでに存在し、基盤となる Room データベースでウィジェットの状態を保持するとともに、Hilt を利用してライフサイクルを管理します。

WidgetModel クラスには、Android によって割り当てられた widgetId、SociaLite に表示される連絡先の contactId、表示される displayName および photo、ブール値(その連絡先の未読メッセージがある場合)が含まれています。これらはコンポーズ可能な関数 SociaLiteAppWidget によって使用され、ウィジェットに表示されます。

WidgetModelDao は、SociaLite データベースへのアクセスを抽象化するデータアクセス オブジェクトです。WidgetModelRepository は、WidgetModel インスタンスの作成、読み取り、更新、削除を行うための便利な機能を提供します。これらのクラスは Hilt によって作成され、依存関係インジェクションを使用してアプリに追加されます。

  • app/src/main/java/com/google/android/samples/socialite/widget/model/ にある model パッケージの WidgetModel.kt ファイルを開きます。

これは Entity アノテーション付きの data クラスです。各ウィジェット インスタンスには、Android によって専用の ID が割り当てられます。この ID は、SociaLite によってモデルデータの主キーとして使用されます。モデルの各インスタンスは、関連付けられた連絡先の基本情報と、その連絡先からの未読メッセージがあるかどうかを追跡します。

@Entity(
    foreignKeys = [
        ForeignKey(
            entity = Contact::class,
            parentColumns = ["id"],
            childColumns = ["contactId"],
            onDelete = ForeignKey.CASCADE,
        ),
    ],
    indices = [
        Index("widgetId"),
        Index("contactId"),
    ],
)
data class WidgetModel(
    @PrimaryKey val widgetId: Int,
    val contactId: Long,
    val displayName: String,
    val photo: String,
    val unreadMessages: Boolean = false,
) : WidgetState

ゼロ状態

コンポーズ可能な関数 ContentWidgetModelRepository からウィジェットのモデルを読み込み、使用できるモデルがない場合はゼロ状態を表示し、それ以外の場合はウィジェットの通常のコンテンツを表示します。現時点では「Hello World」メッセージが表示されますが、次のパートでインターフェースを改善します。

コンポーズ可能な関数 Content を、コンポーズ可能な関数 ZeroState またはプレースホルダ Text のいずれかを表示する when 式に置き換えます。

  1. provideGlance メソッドのコンポーズ可能な関数の外側で、WidgetModelRepository への参照と現在のウィジェット ID を取得します。SociaLiteAppWidget provideGlance メソッドの provideContent の前に、次の行を追加します。
override suspend fun provideGlance(context: Context, id: GlanceId) {
   val widgetId = GlanceAppWidgetManager(context).getAppWidgetId(id)
   val repository = WidgetModelRepository.get(context)

また、次のインポートも追加する必要があります。

import com.google.android.samples.socialite.widget.model.WidgetModel
import com.google.android.samples.socialite.widget.model.WidgetModelRepository
import com.google.android.samples.socialite.widget.model.WidgetState.Loading
import androidx.glance.appwidget.GlanceAppWidgetManager
  1. コンポーズ可能な関数 Content で、リポジトリおよびウィジェット ID をパラメータとして追加し、これらを使用してモデルを読み込みます。コンポーズ可能な関数 Content のシグネチャを更新し、次の行を追加します。
private fun Content(repository: WidgetModelRepository, widgetId: Int) {
   val model = repository.loadModel(widgetId).collectAsState(Loading).value
  1. Android Studio で次のインポートが自動的に追加されない場合は、手動で追加します。
import androidx.compose.runtime.collectAsState

また、Content にウィジェット ID とリポジトリを渡すように provideGlance を更新する必要があります。

provideGlance を次のコードで置き換えます。

override suspend fun provideGlance(context: Context, id: GlanceId) {
        val widgetId = GlanceAppWidgetManager(context).getAppWidgetId(id)
        val repository = WidgetModelRepository.get(context)

        provideContent {
            GlanceTheme {
                Content(repository, widgetId)
            }
        }
    }
  1. コンポーズ可能な関数 Content で、モデルが存在するかどうかに基づいて、どの状態を表示するかを決定します。Scaffold コンポーネントとそのコンテンツを次の when ブロックに置き換えることで、Scaffold とウィジェットのコンテンツをコンポーズ可能な関数 ZeroState に移動します。
   when (model) {
       is WidgetModel -> {Text("Hello World")}
       else -> ZeroState(widgetId)
   }

コンポーズ可能な関数 ZeroStatecom.google.android.samples.socialite.widget.ui パッケージのスターター コードにすでに含まれています。

  1. Android Studio で com.google.android.samples.socialite.widget.ui パッケージが自動的にインポートされなかった場合は、SociaLiteAppWidget のインポート セクションに次のコードを追加します。
import com.google.android.samples.socialite.widget.ui.ZeroState
  1. 更新を確認するには、アプリを再実行してから、ウィジェットの新しいコピーをホーム画面に追加します。ウィジェットに ZeroState コンポーネントとボタンが表示されます。ボタンをクリックすると構成アクティビティが開きます。次のセクションで、このアクティビティからウィジェットの状態を更新します。

SociaLite ウィジェットのゼロ状態が表示されている Android のホーム画面。ゼロ状態にはボタンが 1 つあります。

4 件の連絡先候補が表示されている SociaLite AppWidget の構成アクティビティ。

構成アクティビティ

コンポーズ可能な関数 ZeroState を確認します。この関数は、ZeroState.kt ファイルのパッケージ com.google.android.samples.socialite.widget.ui にあります。

@Composable
fun ZeroState(widgetId: Int) {
   val widgetIdKey = ActionParameters.Key<Int>(AppWidgetManager.EXTRA_APPWIDGET_ID)
Scaffold(
   titleBar = {
       TitleBar(
           modifier = GlanceModifier.clickable(actionStartActivity(MainActivity::class.java)),
           textColor = GlanceTheme.colors.onSurface,
           startIcon = ImageProvider(R.drawable.ic_launcher_monochrome),
           title = "SociaLite",
       )
   },
   backgroundColor = GlanceTheme.colors.widgetBackground,
   modifier = GlanceModifier.fillMaxSize(),
) {
   Box(modifier = GlanceModifier.fillMaxSize(), contentAlignment = Alignment.Center) {
       Button(
           text = "Select Favorite Contact",
           onClick = actionStartActivity<SociaLiteAppWidgetConfigActivity>(
               parameters = actionParametersOf(widgetIdKey to widgetId),
           ),
       )
   }
}

}

コンポーズ可能な関数 Scaffold はコンポーズ可能な関数 ZeroState に移動されます。TitleBar には SociaLite のメイン アクティビティを開く clickable 修飾子があります。ZeroState は Glance のコンポーズ可能な関数 Button を使用して行動を促すフレーズをユーザーに表示します。ボタンがクリックされると SociaLiteAppWidgetConfigActivity アクティビティが開きますが、これにはウィジェット ID がインテント エクストラとして含まれています。これらのアクションの両方で、Glance の actionStartActivity 簡易関数が使用されます。アクションについて詳しくは、ユーザー操作を処理するをご覧ください。

  1. ウィジェットの構成を更新するのに SociaLiteAppWidgetConfigActivity がどのように使用されるかを確認します。このクラスはウィジェットの構成アクティビティでもあります。構成アクティビティは、キー AppWidgetManager.*EXTRA_APPWIDGET_ID.* を使用して整数のインテント エクストラを読み取ります。構成アクティビティについて詳しくは、ユーザーがアプリ ウィジェットを構成できるようにするをご覧ください。
  2. SociaLiteAppWidgetConfigActivity で、ContactRow onClick プロパティの TODO を次のコードに置き換えます。
{
  coroutineScope.launch {

     widgetModelRepository.createOrUpdate(
       WidgetModel(
           appWidgetId,
           contact.id,
           contact.name,
           contact.iconUri.toString(),
           false,
       ),
     )
     SociaLiteAppWidget().updateAll(this@SociaLiteAppWidgetConfigActivity)
     val resultValue = Intent().putExtra(
       AppWidgetManager.EXTRA_APPWIDGET_ID,
       appWidgetId,
     )
     setResult(RESULT_OK, resultValue)
     finish()
  }
}

Android Studio で自動的にインポートされない場合は、以下を追加します。

import com.google.android.samples.socialite.widget.model.WidgetModel
import androidx.glance.appwidget.updateAll
import kotlinx.coroutines.launch

このコードブロックはウィジェットの状態を更新します。まず、リポジトリを使用して WidgetModel を保存するか、選択した連絡先の情報で更新します。次に、updateAll suspend 関数を呼び出します。これはホーム画面上のすべてのウィジェットを更新する関数で、アプリの任意の場所から呼び出すことができます。このブロックは最後に、ウィジェットが正常に更新されたことを示すため、構成アクティビティの結果を設定します。

  1. ホーム画面でウィジェットを実行して置き換えると、新しいゼロ状態が表示されます。

SociaLite ウィジェットのゼロ状態が表示されている Android のホーム画面。ゼロ状態にはボタンが 1 つあります。

  1. [Select favorite contact] をクリックすると、構成アクティビティに移動します。

4 件の連絡先候補が表示されている構成アクティビティのスクリーンショット Hello World ウィジェットがライトモードで表示されている Android のホーム画面

  1. 連絡先を選択すると、ウィジェットが更新されます。ウィジェットにはまだお気に入りの連絡先が表示されませんが、この機能は次のセクションで追加します。

ウィジェットのデータを管理する

  1. [App Inspection] ツールを開き、必要に応じてプロセスに接続し、[Database Inspector] タブを選択してアプリのデータベースのコンテンツを確認します。
  2. ウィジェットでお気に入りの連絡先を選択し、「Hello World」に更新されることを確認します。App Inspection ツールに戻ると、[WidgetModel] タブにウィジェットのエントリが 1 件表示されているはずです。場合によっては、表を更新するか、[Live updates] をクリックして変更を確認する必要があります。

dd030cce6a75be25.png

  1. 別のウィジェットを追加し、別の連絡先を選択します。新しいモデルを表示するには、[Refresh table] または [Live updates] をクリックする必要がある場合があります。
  2. ウィジェットを削除しても、モデルはまだデータベースに残っています。

ウィジェットの削除時に onDeleted をオーバーライドすることで、SociaLiteAppWidgetReceiver を更新してデータベースをクリーンアップできます。

孤立したウィジェット モデルをクリーンアップするには、WidgetModelRepository.cleanupWidgetModels を呼び出します。このリポジトリ クラスは Hilt によって管理されており、そのインスタンスにアクセスするには依存関係インジェクションを使用する必要があります。

  1. SociaLiteAppWidgetReceiver で、レシーバクラス宣言に AndroidEntryPoint Hilt アノテーションを追加し、WidgetModelRepository インスタンスを注入します。
  2. onDeleted のメソッド オーバーライドで WidgetModelRepository.cleanupWidgetModels を呼び出します。

コードの内容は次のようになります。

package com.google.android.samples.socialite.widget

import android.content.Context
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import com.google.android.samples.socialite.widget.model.WidgetModelRepository
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class SociaLiteAppWidgetReceiver : GlanceAppWidgetReceiver() {
   override val glanceAppWidget: GlanceAppWidget = SociaLiteAppWidget()

   @Inject
   lateinit var repository: WidgetModelRepository

   override fun onDeleted(context: Context, appWidgetIds: IntArray) {
       super.onDeleted(context, appWidgetIds)
       repository.cleanupWidgetModels(context)
   }

}
  1. アプリを再実行します。ホーム画面からウィジェットが削除されると、App inspector のモデル行が削除されます。

5. 連絡先の UI を追加し、新しいメッセージが届いたら更新する

この Codelab の最後のセクションでは、ウィジェットに連絡先の UI の最終版を実装し、その連絡先からの未読メッセージがある場合に UI を更新します。

  1. model パッケージの WidgetModelRepository クラスを確認します。

このクラスの updateUnreadMessagesForContact コンビニエンス メソッドを使用して、連絡先の ID に関連付けられたウィジェットを更新します。

//Don't add this code.
fun updateUnreadMessagesForContact(contactId: Long, unread: Boolean) {
   coroutineScope.launch {
       widgetModelDao.modelsForContact(contactId).filterNotNull().forEach { model ->
           widgetModelDao.update(
             WidgetModel(model.widgetId, model.contactId, model.displayName, model.photo, unread)
           )
           SociaLiteAppWidget().updateAll(appContext)
       }
   }
}

このメソッドには 2 つのパラメータがあります。contactId は更新される連絡先の ID で、unread は未読メッセージの状態のブール値です。このメソッドは WidgetModelDao を使用して、この連絡先を表示するすべてのウィジェット モデルを検索し、新しい既読状態でモデルを更新します。その後、この関数は Glance が提供する SociaLiteAppWidget().updateAll メソッドを呼び出し、ユーザーのホーム画面にあるすべてのウィジェットを更新します。

ウィジェットとその状態が更新される仕組みがわかったところで、連絡先の UI を作成してメッセージを送信し、更新されるか確認しましょう。これを行うには、ウィジェット レイアウトの SociaLiteAppWidget をコンポーズ可能な関数 FavoriteContact で更新します。このレイアウトで、No new messagesNew Messages! のどちらを表示すべきかを確認できます。

  1. com.google.android.samples.socialite.widget.ui パッケージの FavoriteContact.kt ファイルを確認します。
//Don't add this code.
@Composable
fun FavoriteContact(model: WidgetModel, onClick: Action) {
   Column(
       modifier = GlanceModifier.fillMaxSize().clickable(onClick)
           .background(GlanceTheme.colors.widgetBackground).appWidgetBackground()
           .padding(bottom = 8.dp),
       verticalAlignment = Alignment.Vertical.Bottom,
       horizontalAlignment = Alignment.Horizontal.CenterHorizontally,
   ) {
       Image(
           modifier = GlanceModifier.fillMaxWidth().wrapContentHeight().defaultWeight()
               .cornerRadius(16.dp),
           provider = ImageProvider(model.photo.toUri()),
           contentScale = ContentScale.Crop,
           contentDescription = model.displayName,
       )
       Column(
           modifier = GlanceModifier.fillMaxWidth().wrapContentHeight().padding(top = 4.dp),
           verticalAlignment = Alignment.Vertical.Bottom,
           horizontalAlignment = Alignment.Horizontal.CenterHorizontally,
       ) {
           Text(
               text = model.displayName,
               style = TextStyle(
                   fontWeight = FontWeight.Bold,
                   fontSize = 24.sp,
                   color = (GlanceTheme.colors.onSurface),
               ),
           )

           Text(
               text = if (model.unreadMessages) "New Message!" else "No messages",
               style = TextStyle(
                   fontWeight = FontWeight.Bold,
                   fontSize = 16.sp,
                   color = (GlanceTheme.colors.onSurface),
               ),
           )
       }
   }
}
  1. SociaLiteAppWidget のコンポーズ可能な関数 ContentText("Hello World") を、コンポーズ可能な関数 FavoriteContact の呼び出しに置き換えます。

このコンポーズ可能な関数は、Glance 関数 actionStartActivity によって作成された WidgetModel と Action を受け取ります。

  1. モデルが WidgetModel でない場合は、ZeroState の前に when ブロックの呼び出しを追加します。
when (model) {
  is WidgetModel -> FavoriteContact(model = model, onClick = actionStartActivity(
     Intent(LocalContext.current.applicationContext, MainActivity::class.java)
         .setAction(Intent.ACTION_VIEW)
         .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
         .setData("https://socialite.google.com/chat/${model.contactId}".toUri()))
  )
  else -> ZeroState(widgetId)
}
  1. Android Studio で以下のインポートが自動的に追加されない場合は、ここでインポートします。
import com.google.android.samples.socialite.widget.ui.FavoriteContact
import androidx.glance.appwidget.action.actionStartActivity
import android.content.Intent
import com.google.android.samples.socialite.MainActivity
import androidx.core.net.toUri
  1. アプリを実行します。
  2. お気に入りの連絡先を選択し、メッセージを送信し、返信が届く前に即座にアプリを終了します。返信が届くとウィジェットの状態が変わるはずです。
  3. ウィジェットをクリックしてチャットを開き、メイン画面に戻るときに状態が再度更新されていることを確認します。

ライトモードの最終版 SociaLite ウィジェットが表示されている Android のホーム画面

6. 完了

以上でこの Codelab は終了です。Glance を使用してウィジェットを作成する方法を学びました。ホーム画面に合わせて外観を最適化し、ユーザー入力を処理し、自身を更新するウィジェットを作成する方法を理解できたはずです。

main ブランチで解答コードを取得する手順は次のとおりです。

  1. SociaLite をダウンロード済みの場合は次のコマンドを実行します。
git checkout main
  1. SocialLite をまだダウンロードしていない場合は、main ブランチを表示するためもう一度コードをダウンロードします。
git clone git@github.com:android/socialite.git

詳細