はじめに
ウィジェットのクリックイベントの処理は特殊で,ブロードキャストを行うことで実装します.この記事では複数のクリックイベントを扱う際の注意点と,Oreoからの暗黙的なブロードキャストの制限についての対策を書いていきます.
※ ウィジェットのクリックイベントの処理方法をある程度知っていることが前提の記事です
PendingIntentの作成
ここで注意すべきことは次の3つです.
-
Intentを明示的にする -
requestCodeを一意にする - ブロードキャスト受け取り時の処理分岐を可能にする
以下のコードでIntentとrequestCodeの作成をします.
private enum class ClickType {
TYPE1, TYPE2, TYPE3
}
private fun createIntentAndRequestCode(context: Context, appWidgetId: Int, type: ClickType): Pair<Intent, Int>{
val intent = Intent(context, WidgetCounter::class.java).apply {
putExtra(APP_WIDGET_ID, appWidgetId)
putExtra(CLICK_TYPE, type)
}
val requestCode = appWidgetId * ClickType.values().size + type.ordinal
return Pair(intent, requestCode)
}
private const val CLICK_TYPE = "clickType"
private const val APP_WIDGET_ID = "appWidgetId"
その後,PendingIntent.getBroadcast()でブロードキャストするPendingIntentを生成し,RemoteViews.setOnClickPendingIntent()でクリックが発生した時の処理をセットします.
val (intent, code) = createIntentAndRequestCode(context, appWidgetId, ClickType.TYPE1)
// viewsはウィジェット本体(RemoteViews)
views.setOnClickPendingIntent(R.id.layout_id, // クリックイベントを設定するviewのID
PendingIntent.getBroadcast(context, code, intent, PendingIntent.FLAG_UPDATE_CURRENT))
Intentを明示的にする
Oreoから暗黙的なブロードキャストに制限がかかるため,自身(AppWidgetProvider)に対して明示的なブロードキャストする必要があります.Intent(context, WidgetCounter::class.java)の様にクラスを直接指定します.
requestCodeを一意にする
ブロードキャストするPendingIntentには一意なrequestCodeを割り当てる必要があります.ウィジェットの一意なIDであるappWidgetIdを利用することで,一意なrequestCodeを生成することが出来ます.
val requestCode = appWidgetId * ClickType.values().size + type.ordinal
後述するClickTypeが異なればappWidgetIdが同じでもrequestCodeが区別出来るようにするため,ClickTypeごとにtype.ordinalで値をずらしています.
ブロードキャスト受け取り時の処理分岐を可能にする
処理分岐のためにenum classであるClickTypeを定義します.Intの定数を複数定義して,その値によって処理を分岐させても良いのですが,enum classの方が便利なのでこちらを利用しました.IntentにputExtra(CLICK_TYPE, type)でClickTypeを渡して,ブロードキャスト受け取り時に分岐が出来るようにします.
onReceiveでイベント処理
onReceive では,クリックイベント以外のブロードキャストも受け取ってしまう点に注意します.ウィジェットの更新を行うACTION_APPWIDGET_UPDATEなどのIntentも受け取り,スーパークラスで処理されるため,フィルタリングをする必要があります.
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent) // ACTION_APPWIDGET_UPDATEなどのIntentが処理される
if (!intent.hasExtra(APP_WIDGET_ID) || !intent.hasExtra(CLICK_TYPE)) return
val appWidgetId = intent.getIntExtra(APP_WIDGET_ID, 0)
when (intent.getSerializableExtra(CLICK_TYPE) as ClickType) {
ClickType.TYPE1 -> {
// 処理1
}
ClickType.TYPE2 -> {
// 処理2
}
ClickType.TYPE3 -> {
// 処理3
}
}
}
おわりに
明示的なブロードキャストという不思議なことをしていますが,これでOreoでも動くようになります.同じrequestCodeを利用したために,処理分岐が出来ないというミスで数時間無駄にしてしまったので,同様の原因で詰まっている人の助けになればいいなと思います.