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

Take a look at Jetpack Glance, London 10.22

Piotr Prus
November 07, 2022

Take a look at Jetpack Glance, London 10.22

Jetpack Glance is the new shiny library in the Jetpack family for creating widgets. At first glance, it resembles a Jetpack Compose, but in many cases, it is really different. Learn those differences and create beautiful widgets easily.
During this talk, I will describe how the Jetpack Glance works, what are the differences between it and RemoteViews widgets. You will also learn why we still need some XMLs, how to manage the state, and periodic updates.

Piotr Prus

November 07, 2022
Tweet

More Decks by Piotr Prus

Other Decks in Programming

Transcript

  1. Agenda: • Introduction to widgets • Widget providers • Composing

    widget UI • State management • Trigger widget update • Change state outside widget class • Initial con fi guration of widget (Con fi guration Activity) • App launch from widget • Why we still need XML • Thoughts and conclusion
  2. androidx.glance:glance-appwidget:1.0.0-alpha05 android { buildFeatures { compose = true } composeOptions

    { kotlinCompilerExtensionVersion = "1.1.0-beta03" } kotlinOptions { jvmTarget = "1.8" } }
  3. class TestWidgetProvider : AppWidgetProvider() { override fun onReceive(context: Context?, intent:

    Intent?) { super.onReceive(context, intent) } override fun onUpdate( context: Context?, appWidgetManager: AppWidgetManager?, appWidgetIds: IntArray? ) { super.onUpdate(context, appWidgetManager, appWidgetIds) } }
  4. class TestWidgetProvider : AppWidgetProvider() { override fun onReceive(context: Context?, intent:

    Intent?) { super.onReceive(context, intent) } override fun onUpdate( context: Context?, appWidgetManager: AppWidgetManager?, appWidgetIds: IntArray? ) { super.onUpdate(context, appWidgetManager, appWidgetIds) } }
  5. class TestWidgetProvider : AppWidgetProvider() { override fun onReceive(context: Context?, intent:

    Intent?) { super.onReceive(context, intent) } override fun onUpdate( context: Context?, appWidgetManager: AppWidgetManager?, appWidgetIds: IntArray? ) { super.onUpdate(context, appWidgetManager, appWidgetIds) } }
  6. class TestWidgetProvider : AppWidgetProvider() { override fun onReceive(context: Context?, intent:

    Intent?) { super.onReceive(context, intent) } override fun onUpdate( context: Context?, appWidgetManager: AppWidgetManager?, appWidgetIds: IntArray? ) { super.onUpdate(context, appWidgetManager, appWidgetIds) } }
  7. class TestWidgetProvider : AppWidgetProvider() { override fun onReceive(context: Context?, intent:

    Intent?) { super.onReceive(context, intent) } override fun onUpdate( context: Context?, appWidgetManager: AppWidgetManager?, appWidgetIds: IntArray? ) { super.onUpdate(context, appWidgetManager, appWidgetIds) } } class WeatherWidgetReceiver : GlanceAppWidgetReceiver() { override val glanceAppWidget: GlanceAppWidget get() = WeatherWidget() }
  8. data class WidgetState( val data: WeatherItem, val loading: Boolean, )

    data class WeatherItem( val address: String? = null, val latitude: Double, val longitude: Double, val temperature: Int, val windSpeed: Int )
  9. class WeatherWidget(private val state: WidgetState) : GlanceAppWidget() { @Composable override

    fun Content() { Box( modifier = GlanceModifier.fillMaxSize() .background(ImageProvider(resId = R.drawable.shape_widget_small)) .appWidgetBackground(), contentAlignment = Alignment.Center ) { WidgetBody(state = state) } } }
  10. class WeatherWidget(private val state: WidgetState) : GlanceAppWidget() { @Composable override

    fun Content() { Box( modifier = GlanceModifier.fillMaxSize() .background(ImageProvider(resId = R.drawable.shape_widget_small)) .appWidgetBackground(), contentAlignment = Alignment.Center ) { WidgetBody(state = state) } } }
  11. class WeatherWidget(private val state: WidgetState) : GlanceAppWidget() { @Composable override

    fun Content() { Box( modifier = GlanceModifier.fillMaxSize() .cornerRadius(8.dp) .appWidgetBackground(), contentAlignment = Alignment.Center ) { WidgetBody(state = state) } } }
  12. class WeatherWidget(private val state: WidgetState) : GlanceAppWidget() { @Composable override

    fun Content() { Box( modifier = GlanceModifier.fillMaxSize() .cornerRadius(8.dp) .appWidgetBackground(), contentAlignment = Alignment.Center ) { WidgetBody(state = state) } } } Min API 31
  13. @Composable fun WidgetBody(state: WidgetState) { Column() { Row() { Image()

    Spacer() Text() Spacer() Image() } Spacer() Row() { Image() Spacer() Text() Spacer() Image() Spacer() Text() } } }
  14. @Composable fun WidgetBody(state: WidgetState) { Column() { Row() { Image()

    Spacer() Text() Spacer() Image() } Spacer() Row() { Image() Spacer() Text() Spacer() Image() Spacer() Text() } } } import androidx.glance.Image import androidx.glance.layout.Column import androidx.glance.layout.Row import androidx.glance.layout.Spacer import androidx.glance.text.Text
  15. Glance components: • Column • LazyColumn • Row • Box

    • Image • CircularProgressIndicator • Text • Button • Spacer Glance modi fi er: • Height • Width • Size • Padding • Background • Corner radius • Visibility • Clickable
  16. Image( modifier = GlanceModifier.size(12.dp), provider = ImageProvider(resId = R.drawable.outline_location_on_24), contentDescription

    = "Location icon" ) Spacer(modifier = GlanceModifier.width(4.dp)) Text( modifier = GlanceModifier.fillMaxWidth().defaultWeight(), text = state.data.address ?: "null", style = TextStyle( fontWeight = FontWeight.Normal, fontSize = 10.sp, textAlign = TextAlign.Start, color = ColorProvider(day = Color.Black, night = Color.White) ), maxLines = 1 )
  17. Image( modifier = GlanceModifier.size(12.dp), provider = ImageProvider(resId = R.drawable.outline_location_on_24), contentDescription

    = "Location icon" ) Spacer(modifier = GlanceModifier.width(4.dp)) Text( modifier = GlanceModifier.fillMaxWidth().defaultWeight(), text = state.data.address ?: "null", style = TextStyle( fontWeight = FontWeight.Normal, fontSize = 10.sp, textAlign = TextAlign.Start, color = ColorProvider(day = Color.Black, night = Color.White) ), maxLines = 1 )
  18. Image( modifier = GlanceModifier.size(12.dp), provider = ImageProvider(resId = R.drawable.outline_location_on_24), contentDescription

    = "Location icon" ) Spacer(modifier = GlanceModifier.width(4.dp)) Text( modifier = GlanceModifier.fillMaxWidth().defaultWeight(), text = state.data.address ?: "null", style = TextStyle( fontWeight = FontWeight.Normal, fontSize = 10.sp, textAlign = TextAlign.Start, color = ColorProvider(day = Color.Black, night = Color.White) ), maxLines = 1 )
  19. Image( modifier = GlanceModifier.size(12.dp), provider = ImageProvider(resId = R.drawable.outline_location_on_24), contentDescription

    = "Location icon" ) Spacer(modifier = GlanceModifier.width(4.dp)) Text( modifier = GlanceModifier.fillMaxWidth().defaultWeight(), text = state.data.address ?: "null", style = TextStyle( fontWeight = FontWeight.Normal, fontSize = 10.sp, textAlign = TextAlign.Start, color = ColorProvider(day = Color.Black, night = Color.White) ), maxLines = 1 )
  20. Image( modifier = GlanceModifier.size(12.dp), provider = ImageProvider(resId = R.drawable.outline_location_on_24), contentDescription

    = "Location icon" ) Spacer(modifier = GlanceModifier.width(4.dp)) Text( modifier = GlanceModifier.fillMaxWidth().defaultWeight(), text = state.data.address ?: "null", style = TextStyle( fontWeight = FontWeight.Normal, fontSize = 10.sp, textAlign = TextAlign.Start, color = ColorProvider(day = Color.Black, night = Color.White) ), maxLines = 1 )
  21. Image( modifier = GlanceModifier.size(12.dp), provider = ImageProvider(resId = R.drawable.outline_location_on_24), contentDescription

    = "Location icon" ) Spacer(modifier = GlanceModifier.width(4.dp)) Text( modifier = GlanceModifier.fillMaxWidth().defaultWeight(), text = state.data.address ?: "null", style = TextStyle( fontWeight = FontWeight.Normal, fontSize = 10.sp, textAlign = TextAlign.Start, color = ColorProvider(day = Color.Black, night = Color.White) ), maxLines = 1 )
  22. Image( modifier = GlanceModifier.size(12.dp), provider = ImageProvider(resId = R.drawable.outline_location_on_24), contentDescription

    = "Location icon" ) Spacer(modifier = GlanceModifier.width(4.dp)) Text( modifier = GlanceModifier.fillMaxWidth().defaultWeight(), text = state.data.address ?: "null", style = TextStyle( fontWeight = FontWeight.Normal, fontSize = 10.sp, textAlign = TextAlign.Start, color = ColorProvider(day = Color.Black, night = Color.White) ), maxLines = 1 )
  23. Image( modifier = GlanceModifier.size(12.dp), provider = ImageProvider(resId = R.drawable.outline_location_on_24), contentDescription

    = "Location icon" ) Spacer(modifier = GlanceModifier.width(4.dp)) Text( modifier = GlanceModifier.fillMaxWidth().defaultWeight(), text = state.data.address ?: "null", style = TextStyle( fontWeight = FontWeight.Normal, fontSize = 10.sp, textAlign = TextAlign.Start, color = ColorProvider(day = Color.Black, night = Color.White) ), maxLines = 1 )
  24. Image( modifier = GlanceModifier.size(12.dp), provider = ImageProvider(resId = R.drawable.outline_location_on_24), contentDescription

    = "Location icon" ) Spacer(modifier = GlanceModifier.width(4.dp)) Text( modifier = GlanceModifier.fillMaxWidth().defaultWeight(), text = state.data.address ?: "null", style = TextStyle( fontWeight = FontWeight.Normal, fontSize = 10.sp, textAlign = TextAlign.Start, color = ColorProvider(day = Color.Black, night = Color.White) ), maxLines = 1 )
  25. Image( modifier = GlanceModifier.size(12.dp), provider = ImageProvider(resId = R.drawable.outline_location_on_24), contentDescription

    = "Location icon" ) Spacer(modifier = GlanceModifier.width(4.dp)) Text( modifier = GlanceModifier.fillMaxWidth().defaultWeight(), text = state.data.address ?: "null", style = TextStyle( fontWeight = FontWeight.Normal, fontSize = 10.sp, textAlign = TextAlign.Start, color = ColorProvider(day = Color.Black, night = Color.White) ), maxLines = 1 )
  26. . . . Spacer(modifier = GlanceModifier.width(4.dp)) Text( modifier = GlanceModifier.fillMaxWidth().defaultWeight(),

    text = state.data.address ?: "null", style = TextStyle( fontWeight = FontWeight.Normal, fontSize = 10.sp, textAlign = TextAlign.Start, color = ColorProvider(day = Color.Black, night = Color.White) ), maxLines = 1 ) Image( modifier = GlanceModifier.size(32.dp).padding(6.dp) .clickable(onClick = TODO()), provider = ImageProvider(resId = R.drawable.ic_refresh), contentDescription = "Refresh icon" )
  27. DataStore Widget GlanceStateDe fi nition Preferences by default GlanceId internal

    data class AppWidgetId(val appWidgetId: Int) : GlanceId
  28. SAVE Preferences prefs[doublePreferencesKey(widgetLatitudeKey)] = 54.40 prefs[doublePreferencesKey(widgetLongitudeKey)] = 18.3 GET val

    latitude = prefs[doublePreferencesKey(widgetLatitudeKey)] ?: Double.MIN_VALUE val longitude = prefs[doublePreferencesKey(widgetLongitudeKey)] ?: Double.MIN_VALUE
  29. object WidgetStateHelper { fun save(prefs: MutablePreferences, state: WeatherItem) {} fun

    saveLocation(prefs: MutablePreferences, latitude: Double, longitude: Double) {} fun isStored(prefs: MutablePreferences, latitude: Double, longitude: Double): Boolean = prefs[doublePreferencesKey(WidgetStateHelper.widgetLatitudeKey)] == latitude && prefs[doublePreferencesKey(WidgetStateHelper.widgetLongitudeKey)] == longitude fun setLoading(prefs: MutablePreferences, loading: Boolean) {} }
  30. class WeatherWidget() : GlanceAppWidget() { @Composable override fun Content() {

    val state = WidgetStateHelper.getState(currentState()) } }
  31. class WeatherWidget() : GlanceAppWidget() { @Composable override fun Content() {

    val state = WidgetStateHelper.getState(currentState()) } } Retrieves the current customisable store for view speci fi c state data as de fi ned by GlanceStateDe fi nition in the surface implementation.
  32. Update state Trigger composition public suspend fun updateAppWidgetState( context: Context,

    glanceId: GlanceId, updateState: suspend (MutablePreferences) -> Unit, ) { updateAppWidgetState(context, PreferencesGlanceStateDefinition, glanceId) { it.toMutablePreferences().apply { updateState(this) } } } public suspend fun update(context: Context, glanceId: GlanceId) { require(glanceId is AppWidgetId) { "The glanceId '$glanceId' is not a valid App Widget glance id" } update(context, AppWidgetManager.getInstance(context), glanceId.appWidgetId) }
  33. How to trigger the update? Image( modifier = GlanceModifier.size(32.dp).padding(6.dp) .clickable(onClick

    = actionRunCallback<WidgetRefreshAction>()), provider = ImageProvider(resId = R.drawable.ic_refresh), contentDescription = "Refresh icon" )
  34. How to trigger the update? Image( modifier = GlanceModifier.size(32.dp).padding(6.dp) .clickable(onClick

    = actionRunCallback<WidgetRefreshAction>()), provider = ImageProvider(resId = R.drawable.ic_refresh), contentDescription = "Refresh icon" )
  35. How to trigger the update? Image( modifier = GlanceModifier.size(32.dp).padding(6.dp) .clickable(onClick

    = actionRunCallback<WidgetRefreshAction>()), provider = ImageProvider(resId = R.drawable.ic_refresh), contentDescription = "Refresh icon" ) class WidgetRefreshAction : ActionCallback { override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) { } }
  36. class WidgetRefreshAction : ActionCallback { override suspend fun onRun(context: Context,

    glanceId: GlanceId, parameters: ActionParameters) { val prefs = WeatherWidget().getAppWidgetState<Preferences>(context, glanceId) val location = WidgetStateHelper.getState(prefs).let { it.data.latitude to it.data.longitude } val oneTimeWorkRequest = OneTimeWorkRequestBuilder<WeatherWidgetWorker>() .setInputData( WeatherWidgetWorker.buildData( latitude = location.first, longitude = location.second ) ) .build() WorkManager.getInstance(context) .enqueue(oneTimeWorkRequest) } }
  37. class WidgetRefreshAction : ActionCallback { override suspend fun onRun(context: Context,

    glanceId: GlanceId, parameters: ActionParameters) { val prefs = WeatherWidget().getAppWidgetState<Preferences>(context, glanceId) val location = WidgetStateHelper.getState(prefs).let { it.data.latitude to it.data.longitude } val oneTimeWorkRequest = OneTimeWorkRequestBuilder<WeatherWidgetWorker>() .setInputData( WeatherWidgetWorker.buildData( latitude = location.first, longitude = location.second ) ) .build() WorkManager.getInstance(context) .enqueue(oneTimeWorkRequest) } }
  38. class WidgetRefreshAction : ActionCallback { override suspend fun onRun(context: Context,

    glanceId: GlanceId, parameters: ActionParameters) { val prefs = WeatherWidget().getAppWidgetState<Preferences>(context, glanceId) val location = WidgetStateHelper.getState(prefs).let { it.data.latitude to it.data.longitude } val oneTimeWorkRequest = OneTimeWorkRequestBuilder<WeatherWidgetWorker>() .setInputData( WeatherWidgetWorker.buildData( latitude = location.first, longitude = location.second ) ) .build() WorkManager.getInstance(context) .enqueue(oneTimeWorkRequest) } }
  39. class WidgetRefreshAction : ActionCallback { override suspend fun onRun(context: Context,

    glanceId: GlanceId, parameters: ActionParameters) { val prefs = WeatherWidget().getAppWidgetState<Preferences>(context, glanceId) val location = WidgetStateHelper.getState(prefs).let { it.data.latitude to it.data.longitude } val oneTimeWorkRequest = OneTimeWorkRequestBuilder<WeatherWidgetWorker>() .setInputData( WeatherWidgetWorker.buildData( latitude = location.first, longitude = location.second ) ) .build() WorkManager.getInstance(context) .enqueue(oneTimeWorkRequest) } }
  40. class WidgetRefreshAction : ActionCallback { override suspend fun onRun(context: Context,

    glanceId: GlanceId, parameters: ActionParameters) { val prefs = WeatherWidget().getAppWidgetState<Preferences>(context, glanceId) val location = WidgetStateHelper.getState(prefs).let { it.data.latitude to it.data.longitude } val oneTimeWorkRequest = OneTimeWorkRequestBuilder<WeatherWidgetWorker>() .setInputData( WeatherWidgetWorker.buildData( latitude = location.first, longitude = location.second ) ) .build() WorkManager.getInstance(context) .enqueue(oneTimeWorkRequest) } }
  41. class WidgetRefreshAction : ActionCallback { override suspend fun onRun(context: Context,

    glanceId: GlanceId, parameters: ActionParameters) { val prefs = WeatherWidget().getAppWidgetState<Preferences>(context, glanceId) val location = WidgetStateHelper.getState(prefs).let { it.data.latitude to it.data.longitude } val oneTimeWorkRequest = OneTimeWorkRequestBuilder<WeatherWidgetWorker>() .setInputData( WeatherWidgetWorker.buildData( latitude = location.first, longitude = location.second ) ) .build() WorkManager.getInstance(context) .enqueue(oneTimeWorkRequest) } }
  42. WeatherWidget().updateAppWidgetState(appContext, glanceId) { prefs -> // Update state } //

    Trigger composition of Content() WeatherWidget().update(appContext, glanceId) private suspend fun updateWidgetState( glanceId: GlanceId, update: (MutablePreferences) -> Unit ) { WeatherWidget().apply { updateAppWidgetState(appContext, glanceId) { update(it) } update(appContext, glanceId) } }
  43. class WeatherWidgetWorker( private val repository: WeatherRepository, private val appContext: Context,

    private val workerParameters: WorkerParameters ) : CoroutineWorker(appContext, workerParameters) {
  44. class WeatherWidgetWorker( private val repository: WeatherRepository, private val appContext: Context,

    private val workerParameters: WorkerParameters ) : CoroutineWorker(appContext, workerParameters) { override suspend fun doWork(): Result { return getLocation()?.let { locationPair -> val glanceId = GlanceAppWidgetManager(appContext) .getGlanceIds(WeatherWidget::class.java).firstOrNull { id -> WeatherWidget().getAppWidgetState<Preferences>( appContext, id ).let { prefs -> WidgetStateHelper.isStored( prefs, latitude = locationPair.first, longitude = locationPair.second ) } } ?: return Result.failure() updateWidgetState(glanceId) { WidgetStateHelper.setLoading(it, true) }
  45. class WeatherWidgetWorker( private val repository: WeatherRepository, private val appContext: Context,

    private val workerParameters: WorkerParameters ) : CoroutineWorker(appContext, workerParameters) { override suspend fun doWork(): Result { return getLocation()?.let { locationPair -> val glanceId = GlanceAppWidgetManager(appContext) .getGlanceIds(WeatherWidget::class.java).firstOrNull { id -> WeatherWidget().getAppWidgetState<Preferences>( appContext, id ).let { prefs -> WidgetStateHelper.isStored( prefs, latitude = locationPair.first, longitude = locationPair.second ) } } ?: return Result.failure() updateWidgetState(glanceId) { WidgetStateHelper.setLoading(it, true) }
  46. class WeatherWidgetWorker( private val repository: WeatherRepository, private val appContext: Context,

    private val workerParameters: WorkerParameters ) : CoroutineWorker(appContext, workerParameters) { override suspend fun doWork(): Result { return getLocation()?.let { locationPair -> val glanceId = GlanceAppWidgetManager(appContext) .getGlanceIds(WeatherWidget::class.java).firstOrNull { id -> WeatherWidget().getAppWidgetState<Preferences>( appContext, id ).let { prefs -> WidgetStateHelper.isStored( prefs, latitude = locationPair.first, longitude = locationPair.second ) } } ?: return Result.failure() updateWidgetState(glanceId) { WidgetStateHelper.setLoading(it, true) }
  47. class WeatherWidgetWorker( private val repository: WeatherRepository, private val appContext: Context,

    private val workerParameters: WorkerParameters ) : CoroutineWorker(appContext, workerParameters) { override suspend fun doWork(): Result { return getLocation()?.let { locationPair -> val glanceId = GlanceAppWidgetManager(appContext) .getGlanceIds(WeatherWidget::class.java).firstOrNull { id -> WeatherWidget().getAppWidgetState<Preferences>( appContext, id ).let { prefs -> WidgetStateHelper.isStored( prefs, latitude = locationPair.first, longitude = locationPair.second ) } } ?: return Result.failure() updateWidgetState(glanceId) { WidgetStateHelper.setLoading(it, true) }
  48. class WeatherWidgetWorker( private val repository: WeatherRepository, private val appContext: Context,

    private val workerParameters: WorkerParameters ) : CoroutineWorker(appContext, workerParameters) { override suspend fun doWork(): Result { return getLocation()?.let { locationPair -> val glanceId = GlanceAppWidgetManager(appContext) .getGlanceIds(WeatherWidget::class.java).firstOrNull { id -> WeatherWidget().getAppWidgetState<Preferences>( appContext, id ).let { prefs -> WidgetStateHelper.isStored( prefs, latitude = locationPair.first, longitude = locationPair.second ) } } ?: return Result.failure() updateWidgetState(glanceId) { WidgetStateHelper.setLoading(it, true) }
  49. class WeatherWidgetWorker( private val repository: WeatherRepository, private val appContext: Context,

    private val workerParameters: WorkerParameters ) : CoroutineWorker(appContext, workerParameters) { override suspend fun doWork(): Result { return getLocation()?.let { locationPair -> val glanceId = GlanceAppWidgetManager(appContext) .getGlanceIds(WeatherWidget::class.java).firstOrNull { id -> WeatherWidget().getAppWidgetState<Preferences>( appContext, id ).let { prefs -> WidgetStateHelper.isStored( prefs, latitude = locationPair.first, longitude = locationPair.second ) } } ?: return Result.failure() updateWidgetState(glanceId) { WidgetStateHelper.setLoading(it, true) }
  50. class WeatherWidgetWorker( private val repository: WeatherRepository, private val appContext: Context,

    private val workerParameters: WorkerParameters ) : CoroutineWorker(appContext, workerParameters) { override suspend fun doWork(): Result { return getLocation()?.let { locationPair -> val glanceId = GlanceAppWidgetManager(appContext) .getGlanceIds(WeatherWidget::class.java).firstOrNull { id -> WeatherWidget().getAppWidgetState<Preferences>( appContext, id ).let { prefs -> WidgetStateHelper.isStored( prefs, latitude = locationPair.first, longitude = locationPair.second ) } } ?: return Result.failure() updateWidgetState(glanceId) { WidgetStateHelper.setLoading(it, true) }
  51. override suspend fun doWork(): Result { … updateWidgetState(glanceId) { WidgetStateHelper.setLoading(it,

    true) } repository.getData(latitude = locationPair.first, longitude = locationPair.second) .onSuccess { item -> updateWidgetState(glanceId) { WidgetStateHelper.save(it, item) } return Result.success() } .onFailure { throwable -> updateWidgetState(glanceId) { WidgetStateHelper.setLoading(it, false) } return Result.retry() }
  52. override suspend fun doWork(): Result { … updateWidgetState(glanceId) { WidgetStateHelper.setLoading(it,

    true) } repository.getData(latitude = locationPair.first, longitude = locationPair.second) .onSuccess { item -> updateWidgetState(glanceId) { WidgetStateHelper.save(it, item) } return Result.success() } .onFailure { throwable -> updateWidgetState(glanceId) { WidgetStateHelper.setLoading(it, false) } return Result.retry() }
  53. override suspend fun doWork(): Result { … updateWidgetState(glanceId) { WidgetStateHelper.setLoading(it,

    true) } repository.getData(latitude = locationPair.first, longitude = locationPair.second) .onSuccess { item -> updateWidgetState(glanceId) { WidgetStateHelper.save(it, item) } return Result.success() } .onFailure { throwable -> updateWidgetState(glanceId) { WidgetStateHelper.setLoading(it, false) } return Result.retry() }
  54. lifecycleScope.launch { val glanceId = GlanceAppWidgetManager(context).getGlanceIds( WeatherWidget::class.java ).last() WeatherWidget().apply {

    updateAppWidgetState(context, glanceId) { WidgetStateHelper.saveLocation(it, latitude = item.latitude, longitude = item.longitude) WidgetStateHelper.saveAddress(it, address = item.name) } update(context, glanceId) } startWeatherWorker(latitude = item.latitude, longitude = item.longitude) setResult(RESULT_OK, intent) finish() } On item click
  55. lifecycleScope.launch { val glanceId = GlanceAppWidgetManager(context).getGlanceIds( WeatherWidget::class.java ).last() WeatherWidget().apply {

    updateAppWidgetState(context, glanceId) { WidgetStateHelper.saveLocation(it, latitude = item.latitude, longitude = item.longitude) WidgetStateHelper.saveAddress(it, address = item.name) } update(context, glanceId) } startWeatherWorker(latitude = item.latitude, longitude = item.longitude) setResult(RESULT_OK, intent) finish() } On item click
  56. lifecycleScope.launch { val glanceId = GlanceAppWidgetManager(context).getGlanceIds( WeatherWidget::class.java ).last() WeatherWidget().apply {

    updateAppWidgetState(context, glanceId) { WidgetStateHelper.saveLocation(it, latitude = item.latitude, longitude = item.longitude) WidgetStateHelper.saveAddress(it, address = item.name) } update(context, glanceId) } startWeatherWorker(latitude = item.latitude, longitude = item.longitude) setResult(RESULT_OK, intent) finish() } On item click
  57. lifecycleScope.launch { val glanceId = GlanceAppWidgetManager(context).getGlanceIds( WeatherWidget::class.java ).last() WeatherWidget().apply {

    updateAppWidgetState(context, glanceId) { WidgetStateHelper.saveLocation(it, latitude = item.latitude, longitude = item.longitude) WidgetStateHelper.saveAddress(it, address = item.name) } update(context, glanceId) } startWeatherWorker(latitude = item.latitude, longitude = item.longitude) setResult(RESULT_OK, intent) finish() } On item click
  58. lifecycleScope.launch { val glanceId = GlanceAppWidgetManager(context).getGlanceIds( WeatherWidget::class.java ).last() WeatherWidget().apply {

    updateAppWidgetState(context, glanceId) { WidgetStateHelper.saveLocation(it, latitude = item.latitude, longitude = item.longitude) WidgetStateHelper.saveAddress(it, address = item.name) } update(context, glanceId) } startWeatherWorker(latitude = item.latitude, longitude = item.longitude) setResult(RESULT_OK, intent) finish() } On item click
  59. lifecycleScope.launch { val glanceId = GlanceAppWidgetManager(context).getGlanceIds( WeatherWidget::class.java ).last() WeatherWidget().apply {

    updateAppWidgetState(context, glanceId) { WidgetStateHelper.saveLocation(it, latitude = item.latitude, longitude = item.longitude) WidgetStateHelper.saveAddress(it, address = item.name) } update(context, glanceId) } startWeatherWorker(latitude = item.latitude, longitude = item.longitude) setResult(RESULT_OK, intent) finish() } On item click
  60. lifecycleScope.launch { val glanceId = GlanceAppWidgetManager(context).getGlanceIds( WeatherWidget::class.java ).last() WeatherWidget().apply {

    updateAppWidgetState(context, glanceId) { WidgetStateHelper.saveLocation(it, latitude = item.latitude, longitude = item.longitude) WidgetStateHelper.saveAddress(it, address = item.name) } update(context, glanceId) } startWeatherWorker(latitude = item.latitude, longitude = item.longitude) setResult(RESULT_OK, intent) finish() } On item click
  61. lifecycleScope.launch { val glanceId = GlanceAppWidgetManager(context).getGlanceIds( WeatherWidget::class.java ).last() WeatherWidget().apply {

    updateAppWidgetState(context, glanceId) { WidgetStateHelper.saveLocation(it, latitude = item.latitude, longitude = item.longitude) WidgetStateHelper.saveAddress(it, address = item.name) } update(context, glanceId) } startWeatherWorker(latitude = item.latitude, longitude = item.longitude) setResult(RESULT_OK, intent) finish() } On item click
  62. fun Context.startWeatherWorker(latitude: Double, longitude: Double) { val networkConstraint = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()

    val request = PeriodicWorkRequest .Builder(WeatherWidgetWorker::class.java, 15, TimeUnit.MINUTES) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5000L, TimeUnit.MILLISECONDS) .setInputData( WeatherWidgetWorker.buildData(latitude, longitude) ) .setConstraints(networkConstraint) .build() val uniqueTag = WeatherWidget.UNIQUE_WORK_TAG + "_$latitude" + "_$longitude" WorkManager.getInstance(this) .enqueueUniquePeriodicWork( uniqueTag, ExistingPeriodicWorkPolicy.KEEP, request ) }
  63. fun Context.startWeatherWorker(latitude: Double, longitude: Double) { val networkConstraint = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()

    val request = PeriodicWorkRequest .Builder(WeatherWidgetWorker::class.java, 15, TimeUnit.MINUTES) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5000L, TimeUnit.MILLISECONDS) .setInputData( WeatherWidgetWorker.buildData(latitude, longitude) ) .setConstraints(networkConstraint) .build() val uniqueTag = WeatherWidget.UNIQUE_WORK_TAG + "_$latitude" + "_$longitude" WorkManager.getInstance(this) .enqueueUniquePeriodicWork( uniqueTag, ExistingPeriodicWorkPolicy.KEEP, request ) }
  64. fun Context.startWeatherWorker(latitude: Double, longitude: Double) { val networkConstraint = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()

    val request = PeriodicWorkRequest .Builder(WeatherWidgetWorker::class.java, 15, TimeUnit.MINUTES) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5000L, TimeUnit.MILLISECONDS) .setInputData( WeatherWidgetWorker.buildData(latitude, longitude) ) .setConstraints(networkConstraint) .build() val uniqueTag = WeatherWidget.UNIQUE_WORK_TAG + "_$latitude" + "_$longitude" WorkManager.getInstance(this) .enqueueUniquePeriodicWork( uniqueTag, ExistingPeriodicWorkPolicy.KEEP, request ) }
  65. fun Context.startWeatherWorker(latitude: Double, longitude: Double) { val networkConstraint = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()

    val request = PeriodicWorkRequest .Builder(WeatherWidgetWorker::class.java, 15, TimeUnit.MINUTES) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5000L, TimeUnit.MILLISECONDS) .setInputData( WeatherWidgetWorker.buildData(latitude, longitude) ) .setConstraints(networkConstraint) .build() val uniqueTag = WeatherWidget.UNIQUE_WORK_TAG + "_$latitude" + "_$longitude" WorkManager.getInstance(this) .enqueueUniquePeriodicWork( uniqueTag, ExistingPeriodicWorkPolicy.KEEP, request ) }
  66. fun Context.startWeatherWorker(latitude: Double, longitude: Double) { val networkConstraint = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()

    val request = PeriodicWorkRequest .Builder(WeatherWidgetWorker::class.java, 15, TimeUnit.MINUTES) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5000L, TimeUnit.MILLISECONDS) .setInputData( WeatherWidgetWorker.buildData(latitude, longitude) ) .setConstraints(networkConstraint) .build() val uniqueTag = WeatherWidget.UNIQUE_WORK_TAG + "_$latitude" + "_$longitude" WorkManager.getInstance(this) .enqueueUniquePeriodicWork( uniqueTag, ExistingPeriodicWorkPolicy.KEEP, request ) }
  67. fun Context.startWeatherWorker(latitude: Double, longitude: Double) { val networkConstraint = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()

    val request = PeriodicWorkRequest .Builder(WeatherWidgetWorker::class.java, 15, TimeUnit.MINUTES) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5000L, TimeUnit.MILLISECONDS) .setInputData( WeatherWidgetWorker.buildData(latitude, longitude) ) .setConstraints(networkConstraint) .build() val uniqueTag = WeatherWidget.UNIQUE_WORK_TAG + "_$latitude" + "_$longitude" WorkManager.getInstance(this) .enqueueUniquePeriodicWork( uniqueTag, ExistingPeriodicWorkPolicy.KEEP, request ) }
  68. fun Context.startWeatherWorker(latitude: Double, longitude: Double) { val networkConstraint = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()

    val request = PeriodicWorkRequest .Builder(WeatherWidgetWorker::class.java, 15, TimeUnit.MINUTES) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5000L, TimeUnit.MILLISECONDS) .setInputData( WeatherWidgetWorker.buildData(latitude, longitude) ) .setConstraints(networkConstraint) .build() val uniqueTag = WeatherWidget.UNIQUE_WORK_TAG + "_$latitude" + "_$longitude" WorkManager.getInstance(this) .enqueueUniquePeriodicWork( uniqueTag, ExistingPeriodicWorkPolicy.KEEP, request ) }
  69. fun Context.startWeatherWorker(latitude: Double, longitude: Double) { val networkConstraint = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()

    val request = PeriodicWorkRequest .Builder(WeatherWidgetWorker::class.java, 15, TimeUnit.MINUTES) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5000L, TimeUnit.MILLISECONDS) .setInputData( WeatherWidgetWorker.buildData(latitude, longitude) ) .setConstraints(networkConstraint) .build() val uniqueTag = WeatherWidget.UNIQUE_WORK_TAG + "_$latitude" + "_$longitude" WorkManager.getInstance(this) .enqueueUniquePeriodicWork( uniqueTag, ExistingPeriodicWorkPolicy.KEEP, request ) }
  70. Box( modifier = GlanceModifier.fillMaxSize() .background(ImageProvider(resId = R.drawable.shape_widget_small)) .appWidgetBackground() .clickable( onClick

    = actionStartActivity( activity = MainActivity::class.java, parameters = actionParametersOf( ActionParameters.Key<Double>(WidgetConst.LOCATION_WIDGET_LATITUDE) to state.data.latitude, ActionParameters.Key<Double>(WidgetConst.LOCATION_WIDGET_LONGITUDE) to state.data.longitude, ActionParameters.Key<String>(WidgetConst.WIDGET_NAME_KEY) to (state.data.address ?: "") ) ) ),
  71. Box( modifier = GlanceModifier.fillMaxSize() .background(ImageProvider(resId = R.drawable.shape_widget_small)) .appWidgetBackground() .clickable( onClick

    = actionStartActivity( activity = MainActivity::class.java, parameters = actionParametersOf( ActionParameters.Key<Double>(WidgetConst.LOCATION_WIDGET_LATITUDE) to state.data.latitude, ActionParameters.Key<Double>(WidgetConst.LOCATION_WIDGET_LONGITUDE) to state.data.longitude, ActionParameters.Key<String>(WidgetConst.WIDGET_NAME_KEY) to (state.data.address ?: "") ) ) ),
  72. Box( modifier = GlanceModifier.fillMaxSize() .background(ImageProvider(resId = R.drawable.shape_widget_small)) .appWidgetBackground() .clickable( onClick

    = actionStartActivity( activity = MainActivity::class.java, parameters = actionParametersOf( ActionParameters.Key<Double>(WidgetConst.LOCATION_WIDGET_LATITUDE) to state.data.latitude, ActionParameters.Key<Double>(WidgetConst.LOCATION_WIDGET_LONGITUDE) to state.data.longitude, ActionParameters.Key<String>(WidgetConst.WIDGET_NAME_KEY) to (state.data.address ?: "") ) ) ),
  73. initial_widget_layout.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="12dp"> <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content"

    android:layout_gravity="center_horizontal" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="Loading data..." /> </LinearLayout>
  74. Thoughts and conclusion • We needed this! • It follows

    composable syntax • Supports android 12 features • It is in alpha5 🙁 • There are some examples and recommendations, 
 
but not a full guidelines yet.