はじめに
ウィジェットのクリックイベントの処理は特殊で,ブロードキャストを行うことで実装します.この記事では複数のクリックイベントを扱う際の注意点と,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
を利用したために,処理分岐が出来ないというミスで数時間無駄にしてしまったので,同様の原因で詰まっている人の助けになればいいなと思います.