Manage and update GlanceAppWidget

The following sections describe how to update GlanceAppWidget and manage its state.

Manage GlanceAppWidget state

The provided GlanceAppWidget class is instantiated whenever the widget is created or requires an update, so it should be stateless and passive.

The concept of state can be divided into the following:

  • Application state: The state or content of the app that is required by the widget. For example, a list of stored destinations (i.e., database) defined by the user.
  • Glance state: The specific state that is only relevant to the app widget and does not necessarily modify or affect the app's state. For example, a checkbox was selected in the widget or a counter was increased.

Use application state

App widgets should be passive. Each application is responsible for managing the data layer and handling the states, such as idle, loading, and error reflecting in the widget UI.

For example, the following code retrieves the destinations from the in-memory cache from the repository layer, provides the stored list of destinations, and displays a different UI depending on its state:

class DestinationAppWidget : GlanceAppWidget() {

    // ...

    @Composable
    fun MyContent() {
        val repository = remember { DestinationsRepository.getInstance() }
        // Retrieve the cache data everytime the content is refreshed
        val destinations by repository.destinations.collectAsState(State.Loading)

        when (destinations) {
            is State.Loading -> {
                // show loading content
            }

            is State.Error -> {
                // show widget error content
            }

            is State.Completed -> {
                // show the list of destinations
            }
        }
    }
}

Whenever the state or the data changes, it is the app's responsibility to notify and update the widget. See Update GlanceAppWidget for more information.

Update GlanceAppWidget

As explained in the Manage GlanceAppWidget state section, app widgets are hosted in a different process. Glance translates the content into actual RemoteViews and sends them to the host. To update the content, Glance must recreate the RemoteViews and send them again.

To send the update, call the update method of the GlanceAppWidget instance, providing the context and the glanceId:

MyAppWidget().update(context, glanceId)

To obtain the glanceId, query the GlanceAppWidgetManager:

val manager = GlanceAppWidgetManager(context)
val widget = GlanceSizeModeWidget()
val glanceIds = manager.getGlanceIds(widget.javaClass)
glanceIds.forEach { glanceId ->
    widget.update(context, glanceId)
}

Alternatively, use one of the GlanceAppWidget update extensions:

// Updates all placed instances of MyAppWidget
MyAppWidget().updateAll(context)

// Iterate over all placed instances of MyAppWidget and update if the state of
// the instance matches the given predicate
MyAppWidget().updateIf<State>(context) { state ->
    state == State.Completed
}

These methods can be called from any part of your application. Because they are suspend functions, we recommend launching them outside of the main thread scope. In the following example, they are launched in a CoroutineWorker:

class DataSyncWorker(
    val context: Context,
    val params: WorkerParameters,
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        // Fetch data or do some work and then update all instance of your widget
        MyAppWidget().updateAll(context)
        return Result.success()
    }
}

See Kotlin Coroutines on Android for more details on coroutines.