Upgrade to Pro — share decks privately, control downloads, hide ads and more …

進化したWidget

 進化したWidget

Dosukoi

July 20, 2022
Tweet

More Decks by Dosukoi

Other Decks in Programming

Transcript

  1. これまでのWidget override fun onUpdate( context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray

    ) { val views = RemoteViews( context.packageName, R.layout.app_widget ) views.setTextViewText(R.id.text, "text") views.setImageViewResource(R.id.image, R.drawable.icon) views.setInt( R.id.background, "setBackgroundColor", context.getColor(R.color.white) ) }
  2. これまでのWidget override fun onUpdate( context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray

    ) { val views = RemoteViews( context.packageName, R.layout.app_widget ) views.setTextViewText(R.id.text, "text") views.setImageViewResource(R.id.image, R.drawable.icon) views.setInt( R.id.background, "setBackgroundColor", context.getColor(R.color.white) ) } RemoteView…?
  3. これまでのWidget override fun onUpdate( context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray

    ) { val views = RemoteViews( context.packageName, R.layout.app_widget ) views.setTextViewText(R.id.text, "text") views.setImageViewResource(R.id.image, R.drawable.icon) views.setInt( R.id.background, "setBackgroundColor", context.getColor(R.color.white) ) } RemoteView…? Stringで指定するの...?
  4. これまでのWidget override fun onUpdate( context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray

    ) { val views = RemoteViews( context.packageName, R.layout.app_widget ) views.setTextViewText(R.id.text, "text") views.setImageViewResource(R.id.image, R.drawable.icon) views.setInt( R.id.background, "setBackgroundColor", context.getColor(R.color.white) ) } RemoteView…? Stringで指定するの...? 初見殺しすぎる!
  5. これからのWidget @Composable private fun MainContent( @ColorRes backgroundColorResId: Int, @DrawableRes iconResId:

    Int, text: String, modifier: GlanceModifier = GlanceModifier ) { Row( modifier = GlanceModifier .background(backgroundColorResId) .then(modifier) ) { Image( provider = AndroidResourceImageProvider(resId = iconResId), contentDescription = null ) Spacer(modifier = GlanceModifier.width(8.dp)) Text(text = text) } }
  6. これからのWidget @Composable private fun MainContent( @ColorRes backgroundColorResId: Int, @DrawableRes iconResId:

    Int, text: String, modifier: GlanceModifier = GlanceModifier ) { Row( modifier = GlanceModifier .background(backgroundColorResId) .then(modifier) ) { Image( provider = AndroidResourceImageProvider(resId = iconResId), contentDescription = null ) Spacer(modifier = GlanceModifier.width(8.dp)) Text(text = text) } } 普段Composeで開発してる人なら 慣れ親しんだ記法!
  7. これまでのWidget // AppWidgetProvider override fun onUpdate(…) { val adapterIntent =

    Intent(context, ListViewService::class.java) views.setRemoteAdapter(R.id.list, adapterIntent) } class ListViewService : RemoteViewsService() { override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory { return ListViewFactory() } } class ListViewFactory : RemoteViewsService.RemoteViewsFactory { }
  8. これまでのWidget // AppWidgetProvider override fun onUpdate(…) { val adapterIntent =

    Intent(context, ListViewService::class.java) views.setRemoteAdapter(R.id.list, adapterIntent) } class ListViewService : RemoteViewsService() { override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory { return ListViewFactory() } } class ListViewFactory : RemoteViewsService.RemoteViewsFactory { } 9つのメソッドをoverrideする必要がある
  9. これまでのWidget // AppWidgetProvider override fun onUpdate(…) { val adapterIntent =

    Intent(context, ListViewService::class.java) views.setRemoteAdapter(R.id.list, adapterIntent) } class ListViewService : RemoteViewsService() { override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory { return ListViewFactory() } } class ListViewFactory : RemoteViewsService.RemoteViewsFactory { } 9つのメソッドをoverrideする必要がある ListViewに対してAdapterをセット
  10. これからのWidget @Composable private fun MainContent(items: List<String>) { LazyColumn { items(items

    = items) { item -> Text(text = item) } } } LazyColumnでリスト実装
  11. これからのWidget @Composable private fun MainContent(items: List<String>) { LazyColumn { items(items

    = items) { item -> Text(text = item) } } } LazyColumnでリスト実装 クリックイベントもitemに設定してあげれば良い
  12. これからのWidget @Composable private fun MainContent(items: List<String>) { LazyColumn { items(items

    = items) { item -> Text(text = item) } } } LazyColumnでリスト実装 クリックイベントもitemに設定してあげれば良い ※LazyRowはまだないので注意
  13. これまでのイベントハンドリング companion object { private const val CLICK_INCREMENT_ACTION = "com.example.glance_project.widget.HogeWidgetProvider.CLICK_INCREMENT_ACTION”

    } override fun onUpdate(...) { val intent = Intent(context, AppWidgetProviderSample::class.java).apply { action = CLICK_INCREMENT_ACTION putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds) } val pendingIntent = PendingIntent.getBroadcast( context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) views.setOnClickPendingIntent(R.id.increment_button, pendingIntent) }
  14. これまでのイベントハンドリング companion object { private const val CLICK_INCREMENT_ACTION = "com.example.glance_project.widget.HogeWidgetProvider.CLICK_INCREMENT_ACTION”

    } override fun onUpdate(...) { val intent = Intent(context, AppWidgetProviderSample::class.java).apply { action = CLICK_INCREMENT_ACTION putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds) } val pendingIntent = PendingIntent.getBroadcast( context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) views.setOnClickPendingIntent(R.id.increment_button, pendingIntent) } StringでActionを定義する必要がある
  15. これまでのイベントハンドリング companion object { private const val CLICK_INCREMENT_ACTION = "com.example.glance_project.widget.HogeWidgetProvider.CLICK_INCREMENT_ACTION”

    } override fun onUpdate(...) { val intent = Intent(context, AppWidgetProviderSample::class.java).apply { action = CLICK_INCREMENT_ACTION putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds) } val pendingIntent = PendingIntent.getBroadcast( context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) views.setOnClickPendingIntent(R.id.increment_button, pendingIntent) } StringでActionを定義する必要がある IntentとActionを紐づけてあげる
  16. これまでのイベントハンドリング companion object { private const val CLICK_INCREMENT_ACTION = "com.example.glance_project.widget.HogeWidgetProvider.CLICK_INCREMENT_ACTION”

    } override fun onUpdate(...) { val intent = Intent(context, AppWidgetProviderSample::class.java).apply { action = CLICK_INCREMENT_ACTION putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds) } val pendingIntent = PendingIntent.getBroadcast( context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) views.setOnClickPendingIntent(R.id.increment_button, pendingIntent) } StringでActionを定義する必要がある PendingIntentを発行 IntentとActionを紐づけてあげる
  17. これまでのイベントハンドリング companion object { private const val CLICK_INCREMENT_ACTION = "com.example.glance_project.widget.HogeWidgetProvider.CLICK_INCREMENT_ACTION”

    } override fun onUpdate(...) { val intent = Intent(context, AppWidgetProviderSample::class.java).apply { action = CLICK_INCREMENT_ACTION putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds) } val pendingIntent = PendingIntent.getBroadcast( context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) views.setOnClickPendingIntent(R.id.increment_button, pendingIntent) } StringでActionを定義する必要がある PendingIntentを発行 Viewに対してPendingIntentを登録
  18. これまでのWidget override fun onReceive(context: Context, intent: Intent) { super.onReceive(context, intent)

    when (intent.action) { CLICK_INCREMENT_ACTION -> { // ここにクリック時の処理を書く // Activityを開いたり、Serviceを起動したり、Broadcastを送ったり、Toastを表示したり、なんでもござれ } }
  19. これまでのWidget override fun onReceive(context: Context, intent: Intent) { super.onReceive(context, intent)

    when (intent.action) { CLICK_INCREMENT_ACTION -> { // ここにクリック時の処理を書く // Activityを開いたり、Serviceを起動したり、Broadcastを送ったり、Toastを表示したり、なんでもござれ } } イベントは全てonReceiveに飛んでくる
  20. これまでのWidget override fun onReceive(context: Context, intent: Intent) { super.onReceive(context, intent)

    when (intent.action) { CLICK_INCREMENT_ACTION -> { // ここにクリック時の処理を書く // Activityを開いたり、Serviceを起動したり、Broadcastを送ったり、Toastを表示したり、なんでもござれ } } イベントは全てonReceiveに飛んでくる 定義したActionごとにハンドリング
  21. これからのWidget • Activityを開きたい時 Box(modifier = GlanceModifier.clickable(onClick = actionStartActivity<MainActivity>())) • Broadcastを送りたい時

    Box(modifier = GlanceModifier.clickable(onClick = actionSendBroadcast<HogeBroadcastReceiver>())) • Serviceを開始したい時 Box(modifier = GlanceModifier.clickable(onClick = actionStartService<HogeService>()))
  22. これからのWidget • Activityを開きたい時 Box(modifier = GlanceModifier.clickable(onClick = actionStartActivity<MainActivity>())) • Broadcastを送りたい時

    Box(modifier = GlanceModifier.clickable(onClick = actionSendBroadcast<HogeBroadcastReceiver>())) • Serviceを開始したい時 Box(modifier = GlanceModifier.clickable(onClick = actionStartService<HogeService>())) 初めから用意されている関数を使えば良い
  23. これからのWidget • 独自Actionの定義 Box(modifier = GlanceModifier.clickable(onClick = actionRunCallBack<ClickAction>())) class ClickAction

    : ActionCallback { override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) { // DataStoreの更新やWorkManagerを起動してAPIを叩いたり、なんでもござれ } }
  24. これからのWidget • 独自Actionの定義 Box(modifier = GlanceModifier.clickable(onClick = actionRunCallBack<ClickAction>())) class ClickAction

    : ActionCallback { override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) { // DataStoreの更新やWorkManagerを起動してAPIを叩いたり、なんでもござれ } } 独自Actionを型として定義
  25. これからのWidget • 独自Actionの定義 Box(modifier = GlanceModifier.clickable(onClick = actionRunCallBack<ClickAction>())) class ClickAction

    : ActionCallback { override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) { // DataStoreの更新やWorkManagerを起動してAPIを叩いたり、なんでもござれ } } 独自Actionを型として定義 Modifierで独自Actionを登録してあげれば良い
  26. Primitive型を扱う private val countKey = intPreferencesKey("count") class PrimitiveWidget : GlanceAppWidget()

    { @Composable override fun Content() { val count = currentState(countKey) ?: 0 Column { Text(text = count.toString()) Button( text = "increment", onClick = actionRunCallback<IncrementAction>() ) } } }
  27. Primitive型を扱う private val countKey = intPreferencesKey("count") class PrimitiveWidget : GlanceAppWidget()

    { @Composable override fun Content() { val count = currentState(countKey) ?: 0 Column { Text(text = count.toString()) Button( text = "increment", onClick = actionRunCallback<IncrementAction>() ) } } } DataStoreのKeyを定義
  28. Primitive型を扱う private val countKey = intPreferencesKey("count") class PrimitiveWidget : GlanceAppWidget()

    { @Composable override fun Content() { val count = currentState(countKey) ?: 0 Column { Text(text = count.toString()) Button( text = "increment", onClick = actionRunCallback<IncrementAction>() ) } } } DataStoreのKeyを定義 値を取得
  29. Primitive型を扱う private class IncrementAction : ActionCallback { override suspend fun

    onRun( context: Context, glanceId: GlanceId, parameters: ActionParameters ) { updateAppWidgetState(context, glanceId) { state -> state[countKey] = (state[countKey] ?: 0) + 1 } PrimitiveWidget().update(context, glanceId) } }
  30. Primitive型を扱う private class IncrementAction : ActionCallback { override suspend fun

    onRun( context: Context, glanceId: GlanceId, parameters: ActionParameters ) { updateAppWidgetState(context, glanceId) { state -> state[countKey] = (state[countKey] ?: 0) + 1 } PrimitiveWidget().update(context, glanceId) } } 独自Actionを定義
  31. Primitive型を扱う private class IncrementAction : ActionCallback { override suspend fun

    onRun( context: Context, glanceId: GlanceId, parameters: ActionParameters ) { updateAppWidgetState(context, glanceId) { state -> state[countKey] = (state[countKey] ?: 0) + 1 } PrimitiveWidget().update(context, glanceId) } } 独自Actionを定義 updateAppWidgetStateの中で値を更新
  32. Primitive型を扱う private class IncrementAction : ActionCallback { override suspend fun

    onRun( context: Context, glanceId: GlanceId, parameters: ActionParameters ) { updateAppWidgetState(context, glanceId) { state -> state[countKey] = (state[countKey] ?: 0) + 1 } PrimitiveWidget().update(context, glanceId) } } 独自Actionを定義 updateAppWidgetStateの中で値を更新 Widgetを更新
  33. 独自のデータ型を扱う custom_data.proto syntax = "proto3"; option java_package = "com.example.glance_project"; option

    java_multiple_files = true; message CustomData { int64 id = 1; string title = 2; string description = 3; }
  34. 独自のデータ型を扱う custom_data.proto syntax = "proto3"; option java_package = "com.example.glance_project"; option

    java_multiple_files = true; message CustomData { int64 id = 1; string title = 2; string description = 3; } 独自のデータ型を定義
  35. 独自のデータ型を扱う class CustomDataWidget @Inject constructor() : GlanceAppWidget() { override val

    stateDefinition: GlanceStateDefinition<CustomData> = customDataStateDefinition @Composable override fun Content() { val state = currentState<CustomData>() } } val customDataStateDefinition = object : GlanceStateDefinition<CustomData> { override suspend fun getDataStore( context: Context, fileKey: String ): DataStore<CustomData> = context.customDataStore override fun getLocation(context: Context, fileKey: String): File = context.preferencesDataStoreFile(fileKey) }
  36. 独自のデータ型を扱う class CustomDataWidget @Inject constructor() : GlanceAppWidget() { override val

    stateDefinition: GlanceStateDefinition<CustomData> = customDataStateDefinition @Composable override fun Content() { val state = currentState<CustomData>() } } val customDataStateDefinition = object : GlanceStateDefinition<CustomData> { override suspend fun getDataStore( context: Context, fileKey: String ): DataStore<CustomData> = context.customDataStore override fun getLocation(context: Context, fileKey: String): File = context.preferencesDataStoreFile(fileKey) } DataStoreから取り出す方法を定義
  37. 独自のデータ型を扱う class CustomDataWidget @Inject constructor() : GlanceAppWidget() { override val

    stateDefinition: GlanceStateDefinition<CustomData> = customDataStateDefinition @Composable override fun Content() { val state = currentState<CustomData>() } } val customDataStateDefinition = object : GlanceStateDefinition<CustomData> { override suspend fun getDataStore( context: Context, fileKey: String ): DataStore<CustomData> = context.customDataStore override fun getLocation(context: Context, fileKey: String): File = context.preferencesDataStoreFile(fileKey) } DataStoreから取り出す方法を定義 stateDefinitionをoverrideする
  38. 独自のデータ型を扱う object CustomDataSerializer : Serializer<CustomData> { override val defaultValue: CustomData

    = CustomData.getDefaultInstance() override suspend fun readFrom(input: InputStream): CustomData { try { return CustomData.parseFrom(input) } catch (throwable: Throwable) { throw CorruptionException("Cannot read proto", throwable) } } override suspend fun writeTo(t: CustomData, output: OutputStream) { t.writeTo(output) } } val Context.customDataStore: DataStore<CustomData> by dataStore( fileName = "custom_data.proto", serializer = CustomDataSerializer )
  39. 独自のデータ型を扱う object CustomDataSerializer : Serializer<CustomData> { override val defaultValue: CustomData

    = CustomData.getDefaultInstance() override suspend fun readFrom(input: InputStream): CustomData { try { return CustomData.parseFrom(input) } catch (throwable: Throwable) { throw CorruptionException("Cannot read proto", throwable) } } override suspend fun writeTo(t: CustomData, output: OutputStream) { t.writeTo(output) } } val Context.customDataStore: DataStore<CustomData> by dataStore( fileName = "custom_data.proto", serializer = CustomDataSerializer ) どうやってパースするか定義
  40. 独自のデータ型を扱う object CustomDataSerializer : Serializer<CustomData> { override val defaultValue: CustomData

    = CustomData.getDefaultInstance() override suspend fun readFrom(input: InputStream): CustomData { try { return CustomData.parseFrom(input) } catch (throwable: Throwable) { throw CorruptionException("Cannot read proto", throwable) } } override suspend fun writeTo(t: CustomData, output: OutputStream) { t.writeTo(output) } } val Context.customDataStore: DataStore<CustomData> by dataStore( fileName = "custom_data.proto", serializer = CustomDataSerializer ) どうやってパースするか定義 protoファイルとSerializerの紐付け