Jetpack Glance始め方

# 概要

Jetpack Glanceを使ってウィジェットを作ってみたので覚え書きです。
大体は公式の手順を参照して作ることができますが、躓いたところなどをメモ。

公式の手順は以下の2つ

https://developer.android.com/develop/ui/compose/glance/setup?hl=ja

https://developer.android.com/develop/ui/compose/glance/create-app-widget?hl=ja

# 手順

### ①セットアップ

依存関係など追加します。

```build.gradle
dependencies {
   implementation "androidx.glance:glance-appwidget:1.1.1"
   // Material3ならそれ用のものを使うこと
   implementation "androidx.glance:glance-material:1.1.1"
}
```

### ②メタデータファイルの作成

ウィジェット用のXMLファイルを作成して、メタデータを定義します。

```my_app_widget_info.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/glance_default_loading_layout"
    android:minWidth="110dp"
    android:minHeight="40dp"
    android:previewImage="@null"
    android:resizeMode="none"
    android:updatePeriodMillis="0"
    android:widgetCategory="home_screen" />
```

各属性が何を意味しているかは、以下のページを参考にします。

https://developer.android.com/develop/ui/views/appwidgets?hl=ja&_gl=1*9s0aj5*_up*MQ..*_ga*MzYzOTYyOTU1LjE3NDQwOTc5Nzk.*_ga_6HH9YJMN9M*MTc0NDA5Nzk3OC4xLjAuMTc0NDA5Nzk3OC4wLjAuMTAzOTYyNzUwMQ..#MetaData

initialLayoutはライブラリ側で用意されているものになります。
ウィジェットの初期表示になるので、エラーでうまく読み込めない時はローディング表示から切り替わらなかったりします。

### ③ウィジェットのUI定義とインスタンスの作成

実際のウィジェットのUIはJetpakck Composeにかなり近い感覚で書けます。
ただし同じように使えないものもあるので注意が必要です。
特にstringResourceやcolorResourceが使えないのでcontextから引っ張ってくる必要があるのですが、もし使ってもコンパイルエラーにはなりません。
しかしいざ表示すると「コンテンツを表示できません」と表示されます。

```GlanceSnipetts.kt
private object GlanceCreateAppWidgetSnippet {
    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
        override val glanceAppWidget: GlanceAppWidget = MyAppWidget()
    }

    class MyAppWidget : GlanceAppWidget() {
        override suspend fun provideGlance(context: Context, id: GlanceId) {
            provideContent {
                WidgetContent()
            }
        }

        // ほぼほぼJetpack Composeと同じ要領で書ける
        // コンポーザブルはglance専用のものを使うことに注意
        // 以前のAppWidgetとRemoteViewsの制限に制約されているから
        @SuppressLint("RestrictedApi")
        @Composable
        private fun WidgetContent() {
            Column(
                // ModifierはGlanceModifierを使う
                modifier = GlanceModifier
                    .fillMaxSize()
                    // colorResourceも使えないのでColorProviderを使う
                    .background(ColorProvider(R.color.white)),
                verticalAlignment = Alignment.CenterVertically,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text(
                    text = glanceString(R.string.hoge),
                    modifier = GlanceModifier.padding(12.dp)
                )

                Button(
                    text = "GO!!",
                    onClick = { // TODO }
                )
            }
        }

        // stringResourceは使えないため以下のような関数を用意した方がよさそう
        @Composable
        private fun glanceString(@StringRes id: Int): String {
            return LocalContext.current.getString(id)
        }
    }
}
```

### ④レシーバー設定

ウィジェットの追加や更新は`BroadcastReceiver`経由で行われるので、こちらを設定しておきます。
これを設定しておかないと、②で言及した無限ロード状態になってしまいます。

```GlanceSnipetts.kt
private object GlanceCreateAppWidgetSnippet {
    ...
}

class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
    override val glanceAppWidget: GlanceAppWidget = GlanceCreateAppWidgetSnippet.MyAppWidget()
}
```

### ⑤マニフェストでウィジェットを宣言する

`AndroidManifest.xml`でウィジェットの使用宣言をします。
name属性には④で設定したレシーバーを、メタデータには②で設定したファイルを定義します。

```AndroidManifest.xml
<!-- ⑤マニフェスト宣言 -->
    <application>
        <receiver
            android:name=".hoge.glance.MyAppWidgetReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/my_app_widget_info" />
    </application>
```

以上になります。
あとはアプリの要件に合わせて良い感じにカスタマイズしていきましょう。