大学院生のアプリ開発奮闘記

将来への不安を抱く大学院生二人がアプリ開発に奮闘する困難と過程を綴っていくブログです.現在androidアプリ開発中

AppWidgetProviderで複数のクリックイベントを扱う

はじめに

皆さんこんにちは,shiitaです.
今回は,ウィジェット実装で私が詰まった部分についての話をしたいと思います.

ウィジェットのクリックイベントの処理は特殊で,ブロードキャストを行うことで実装します.この記事では複数のクリックイベントを扱う際の注意点と,Oreoからの暗黙的なブロードキャストの制限についての対策を書いていきます.

ウィジェットのクリックイベントの処理方法をある程度知っていることが前提の記事です

PendingIntentの作成

ここで注意すべきことは次の3つです.

  • Intentを明示的にする
  • requestCodeを一意にする
  • ブロードキャスト受け取り時の処理分岐を可能にする

以下のコードでIntentrequestCodeの作成をします.

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の方が便利なのでこちらを利用しました.IntentputExtra(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を利用したために,処理分岐が出来ないというミスで数時間無駄にしてしまったので,同様の原因で詰まっている人の助けになればいいなと思います.

質問などありましたら,コメントして頂けると嬉しいです.
この記事の内容を活用してCheerAppsが作成した「ウィジェットカウンター」は こちらからダウンロードできます!

play.google.com

ではでは.