はじめに
ホーム画面ウィジェットは、アプリを開かなくても情報を確認できる有効なUIです。
この記事では、アプリにGlance API を用いてウィジェットを追加した際の実装方法を紹介します。
対象読者
-
Jetpack Composeを使ったAndroidアプリ開発経験がある方
-
アプリにウィジェットを追加したい方
対象とするアプリとウィジェットの目的
今回、ウィジェットを追加するアプリは私が個人で開発しているReadTrackというアプリです。
このアプリは積読の解消・読書週間の定着を目的としたアプリで、ユーザーは本の検索と登録、そして登録した本の読書記録の管理を行うことができます。
URL(GitHubリポジトリ)
URL(Play Store)
今回ウィジェットを追加する目的はユーザーにアプリの継続的な利用を促すことです。前述したとおり、このアプリはユーザーに読書週間の定着を目指すアプリであり、アプリを継続的にユーザーに利用してもらうことが重要になります。そのため、直近で登録した本の情報をウィジェットに入れて、ユーザーにアプリの起動を促すようにします。
Glance APIについて
Glance APIは、Androidアプリのホーム画面ウィジェットを実装するライブラリです。
ウィジェットを実装するためのライブラリとして従来のView開発ではRemoteViewsというライブラリが用いられていました。しかし、これには
- XMLファイルを書く必要がある
- 柔軟なUI表現や状態変化を扱いにくい
などの問題が存在していました。
Glance APIはこれを解決し、
- Kotlinのみで記述できる
- 宣言的にUIを記述できる
という特徴を持っています。
詳しくは以下のリンクをご覧ください。
実装の流れ
依存関係の追加
build.gradle(app)に以下の依存関係を追加します。
dependencies{
// For AppWidgets support
implementation("androidx.glance:glance-appwidget:1.1.0")
// For interop APIs with Material 3
implementation("androidx.glance:glance-material3:1.1.0")
}
AppWidgetProviderInfo メタデータを追加する
ウィジェットの基本情報を定義します。android:initialLayout は、ウィジェット表示前に一時的に表示されるレイアウトです。
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="120dp"
android:minHeight="60dp"
android:initialLayout="@layout/glance_default_loading_layout"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen" />
GlanceAppWidgetクラスの拡張クラスを実装
GlanceAppWidgetクラスの拡張クラスを実装する際は、provideGlance関数をオーバーライドする必要があります。この関数はウィジェットのUIを定義する関数であり、ウィジェットのレンダリングに必要なデータを読み込むことができます。
ここで Jetpack Compose と同じ要領でUIを作成できるのGlance APIの利点です。
class MyAppWidget @Inject constructor(
private val repository: DatabaseBooksRepository
) : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
// Roomに保存している本の情報を取得
val books = repository.getAllBooks()
val recentBook = books.maxByOrNull { it.updatedDate }
val bitmap: Bitmap? = recentBook?.let {
withContext(Dispatchers.IO) {
val url = URL(it.thumbnail)
BitmapFactory.decodeStream(url.openStream())
}
}
provideContent {
Column(
modifier = GlanceModifier
.fillMaxSize()
.padding(8.dp)
.background(Color.White),
verticalAlignment = Alignment.CenterVertically,
horizontalAlignment = Alignment.CenterHorizontally
) {
if (recentBook != null && bitmap != null) {
Text(text = "最近更新された本")
Image(
provider = ImageProvider(bitmap),
contentDescription = "最近更新された本のサムネイル",
modifier = GlanceModifier.width(60.dp)
.height(90.dp)
)
Text(text = recentBook.title, maxLines = 1)
} else {
Text("最近更新された本はありません")
}
Button(
text = "アプリを開く",
onClick = actionStartActivity<MainActivity>()
)
}
}
}
}
GlanceAppWidgetReceiverクラスの拡張クラスを実装
ウィジェットの追加や更新はBroadcastRecevier経由で行われます。Glanceでは専用のGlanceAppWidgetReceiverを継承します。
@AndroidEntryPoint
class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
@Inject
lateinit var repository: DatabaseBooksRepository
override val glanceAppWidget: GlanceAppWidget by lazy {
MyAppWidget(repository)
}
}
マニフェストで AppWidget を宣言する
追加したGlanceAppWidgetReceiverをマニフェストで宣言し、Androidシステム側で見えるようにします。
<receiver android:name=".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" />
</receiver>
実装結果
まとめ
個人開発アプリにウィジェットを追加することで、UX向上を実現できました。
ウィジェットは制約が多い一方で、設計を割り切ることでユーザー体験を向上させる有効な手段だと感じました。
おわりに
Glance APIはまだ情報が少ないため、これからウィジェット実装を検討している方の参考になれば幸いです。
