GlanceAppWidget
abstract class GlanceAppWidget
MultiProcessGlanceAppWidget |
MultiProcessGlanceAppWidget can be used with |
Object handling the composition and the communication with AppWidgetManager.
The UI is defined by calling provideContent from within provideGlance. When the widget is requested, the composition is run and translated into a RemoteViews which is then sent to the AppWidgetManager.
Summary
Public constructors |
|---|
GlanceAppWidget(errorUiLayout: @LayoutRes Int) |
Public functions |
|
|---|---|
open Unit |
onCompositionError(A callback invoked when the |
open suspend Unit |
Method called by the framework when an App Widget has been removed from its host. |
abstract suspend Unit |
provideGlance(context: Context, id: GlanceId)Override this function to provide the Glance Composable. |
open suspend Unit |
providePreview(context: Context, widgetCategory: Int)Override this function to provide a Glance Composable that will be used when running this widget in preview mode. |
suspend Unit |
Run the composition in |
Public properties |
|
|---|---|
open PreviewSizeMode |
Defines handling of sizes for previews. |
open SizeMode |
Defines the handling of sizes. |
open GlanceStateDefinition<*>? |
Data store for widget data specific to the view. |
Extension functions |
|
|---|---|
suspend RemoteViews |
GlanceAppWidget.compose(Creates a snapshot of the |
suspend RemoteViews |
GlanceAppWidget.composeForPreview(Runs the composition in |
Flow<RemoteViews> |
@ExperimentalGlanceApiReturns a Flow |
suspend Nothing |
GlanceAppWidget.provideContent(Provides |
suspend Unit |
GlanceAppWidget.updateAll(context: Context)Update all App Widgets managed by the |
suspend inline Unit |
<State : Any?> GlanceAppWidget.updateIf(Update all App Widgets managed by the |
suspend T |
<T : Any?> GlanceAppWidget.getAppWidgetState(Get the state of an App Widget. |
Public constructors
GlanceAppWidget
GlanceAppWidget(
errorUiLayout: @LayoutRes Int = R.layout.glance_error_layout
)
| Parameters | |
|---|---|
errorUiLayout: @LayoutRes Int = R.layout.glance_error_layout |
Used by |
Public functions
onCompositionError
open fun onCompositionError(
context: Context,
glanceId: GlanceId,
appWidgetId: Int,
throwable: Throwable
): Unit
A callback invoked when the AppWidgetSession encounters an exception. At this point, the session will be closed down. The default implementation of this method creates a RemoteViews from errorUiLayout and sets this as the widget's content.
This method should be overridden if you want to log the error, create a custom error layout, or attempt to recover from or ignore the error by updating the widget's view state and then restarting composition.
| Parameters | |
|---|---|
context: Context |
Context. |
glanceId: GlanceId |
The |
appWidgetId: Int |
The appWidgetId of the widget experiencing the error. This is provided as a convenience in addition to |
throwable: Throwable |
The exception that was caught by |
onDelete
open suspend fun onDelete(context: Context, glanceId: GlanceId): Unit
Method called by the framework when an App Widget has been removed from its host.
When the method returns, the state associated with the glanceId will be deleted.
provideGlance
abstract suspend fun provideGlance(context: Context, id: GlanceId): Unit
Override this function to provide the Glance Composable.
This is a good place to load any data needed to render the Composable. Use provideContent to provide the Composable once the data is ready.
provideGlance is run in the background as a androidx.work.CoroutineWorker in response to calls to update and updateAll, as well as requests from the Launcher. Before provideContent is called, provideGlance is subject to the typical androidx.work.WorkManager time limit (currently ten minutes). After provideContent is called, the composition continues to run and recompose for about 45 seconds. When UI interactions or update requests are received, additional time is added to process these requests.
Note: update and updateAll do not restart provideGlance if it is already running. As a result, you should load initial data before calling provideContent, and then observe your sources of data within the composition (e.g. androidx.compose.runtime.collectAsState). This ensures that your widget will continue to update while the composition is active. When you update your data source from elsewhere in the app, make sure to call update in case a Worker for this widget is not currently running.
import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.rememberCoroutineScope import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import androidx.glance.GlanceId import androidx.glance.GlanceModifier import androidx.glance.action.clickable import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.provideContent import androidx.glance.appwidget.updateAll import androidx.glance.text.Text class MyWidget : GlanceAppWidget() { val Context.myWidgetStore by preferencesDataStore("MyWidget") val Name = stringPreferencesKey("name") override suspend fun provideGlance(context: Context, id: GlanceId) { // Load initial data needed to render the AppWidget here. Prefer doing heavy work before // provideContent, as the provideGlance function will timeout shortly after // provideContent is called. val store = context.myWidgetStore val initial = store.data.first() provideContent { // Observe your sources of data, and declare your @Composable layout. val data by store.data.collectAsState(initial) val scope = rememberCoroutineScope() Text( text = "Hello ${data[Name]}", modifier = GlanceModifier.clickable("changeName") { scope.launch { store.updateData { it.toMutablePreferences().apply { set(Name, "Changed") } } } }, ) } } // Updating the widget from elsewhere in the app: suspend fun changeWidgetName(context: Context, newName: String) { context.myWidgetStore.updateData { it.toMutablePreferences().apply { set(Name, newName) } } // Call update/updateAll in case a Worker for the widget is not currently running. This // is not necessary when updating data from inside of the composition using lambdas, // since a Worker will be started to run lambda actions. MyWidget().updateAll(context) } }
import androidx.compose.runtime.collectAsState import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.preferencesDataStore import androidx.glance.GlanceId import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.provideContent import androidx.glance.appwidget.updateAll import androidx.glance.text.Text import androidx.work.CoroutineWorker import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.PeriodicWorkRequest import androidx.work.WorkManager import androidx.work.WorkerParameters class WeatherWidget : GlanceAppWidget() { val Context.weatherWidgetStore by preferencesDataStore("WeatherWidget") val CurrentDegrees = intPreferencesKey("currentDegrees") suspend fun DataStore<Preferences>.loadWeather() { updateData { prefs -> prefs.toMutablePreferences().apply { this[CurrentDegrees] = Random.Default.nextInt() } } } override suspend fun provideGlance(context: Context, id: GlanceId) { coroutineScope { val store = context.weatherWidgetStore val currentDegrees = store.data.map { prefs -> prefs[CurrentDegrees] }.stateIn(this@coroutineScope) // Load the current weather if there is not a current value present. if (currentDegrees.value == null) store.loadWeather() // Create unique periodic work to keep this widget updated at a regular interval. WorkManager.getInstance(context) .enqueueUniquePeriodicWork( "weatherWidgetWorker", ExistingPeriodicWorkPolicy.KEEP, PeriodicWorkRequest.Builder( WeatherWidgetWorker::class.java, 15.minutes.toJavaDuration(), ) .setInitialDelay(15.minutes.toJavaDuration()) .build(), ) // Note: you can also set `android:updatePeriodMillis` to control how often the // launcher requests an update, but this does not support periods less than // 30 minutes. provideContent { val degrees by currentDegrees.collectAsState() Text("Current weather: $degrees °F") } } } } class WeatherWidgetWorker(appContext: Context, params: WorkerParameters) : CoroutineWorker(appContext, params) { override suspend fun doWork(): Result { WeatherWidget().apply { applicationContext.weatherWidgetStore.loadWeather() // Call update/updateAll in case a Worker for the widget is not currently running. updateAll(applicationContext) } return Result.success() } }
providePreview
open suspend fun providePreview(context: Context, widgetCategory: Int): Unit
Override this function to provide a Glance Composable that will be used when running this widget in preview mode. Use provideContent to provide the composable once the data is ready.
In order to generate and publish the previews for a provider, use setWidgetPreviews. You can use composeForPreview to generate a RemoteViews from this Composable without publishing it.
The given widgetCategory value will be one of AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD, or AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX, or some combination of all three. This indicates what kind of widget host this preview can be used for. widgetCategory corresponds to the categories passed to setWidgetPreviews.
import androidx.compose.runtime.Composable import androidx.glance.GlanceId import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.LocalAppWidgetOptions import androidx.glance.appwidget.provideContent import androidx.glance.text.Text class MyWidgetWithPreview : GlanceAppWidget() { override suspend fun provideGlance(context: Context, id: GlanceId) { provideContent { val widgetCategory = LocalAppWidgetOptions.current.getInt( AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY ) Content(isPreview = false, widgetCategory) } } override suspend fun providePreview(context: Context, widgetCategory: Int) { provideContent { Content(isPreview = true, widgetCategory) } } @Composable fun Content(isPreview: Boolean, widgetCategory: Int) { val text = if (isPreview) "preview" else "bound widget" Text("This is a $text.") // Avoid showing personal information if this preview or widget is showing on the // lockscreen/keyguard. val isKeyguardWidget = widgetCategory.and(WIDGET_CATEGORY_KEYGUARD) != 0 if (!isKeyguardWidget) { Text("Some personal info.") } } }
| See also | |
|---|---|
WIDGET_CATEGORY_HOME_SCREEN |
update
suspend fun update(context: Context, id: GlanceId): Unit
Run the composition in provideGlance and send the result to AppWidgetManager.
Public properties
previewSizeMode
open val previewSizeMode: PreviewSizeMode
Defines handling of sizes for previews.
stateDefinition
open val stateDefinition: GlanceStateDefinition<*>?
Data store for widget data specific to the view.
Extension functions
compose
suspend fun GlanceAppWidget.compose(
context: Context,
id: GlanceId = createFakeAppWidgetId(),
options: Bundle? = null,
size: DpSize? = null,
state: Any? = null
): RemoteViews
Creates a snapshot of the GlanceAppWidget content without running recomposition.
This runs the composition one time and translates it to RemoteViews.
If a valid id is provided, this function will use the sizing values from the bound widget if using SizeMode.Exact or SizeMode.Single.
Only one instance of compose for a particular id may run at the same time. Calling compose concurrently with the same ID will succeed, but the first call will resume with an exception.
If you need to call compose concurrently, you can omit id so that a random fake ID will be used. Otherwise, call compose sequentially when using the same id.
composeForPreview
suspend fun GlanceAppWidget.composeForPreview(
context: Context,
widgetCategory: Int,
info: AppWidgetProviderInfo? = null
): RemoteViews
Runs the composition in GlanceAppWidget.providePreview one time and translate it to a RemoteViews. This function can be used to test the preview layout of a GlanceAppWidget.
The value of androidx.glance.LocalSize in the composition depends on the value of GlanceAppWidget.previewSizeMode:
If using SizeMode.Single (default), the composition will use the minimum size of the widget as determined by its AppWidgetProviderInfo.minHeight and AppWidgetProviderInfo.minWidth. If info is null, then DpSize.Zero will be used.
If using SizeMode.Responsive, the composition will use the provided sizes.
The given widgetCategory value should be a combination of AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD, or AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX.
| Parameters | |
|---|---|
context: Context |
context to provide to |
widgetCategory: Int |
widget category to provide to |
info: AppWidgetProviderInfo? = null |
the size of the composition is determined by the minimum width defined in this |
| Returns | |
|---|---|
RemoteViews |
the preview composition translated to a |
runComposition
@ExperimentalGlanceApi
fun GlanceAppWidget.runComposition(
context: Context,
id: GlanceId = createFakeAppWidgetId(),
options: Bundle = Bundle(),
sizes: List<DpSize>? = null,
state: Any? = null,
lambdaReceiver: ComponentName = ComponentName(context, UnmanagedSessionReceiver::class.java)
): Flow<RemoteViews>
Returns a FlowGlanceAppWidget and emits RemoteViews for each result. The composition is closed when the flow is cancelled.
If a valid id is provided, this function will use the sizing values from the bound widget if using SizeMode.Exact or SizeMode.Single.
Lambda actions and list views in the emitted RemoteViews will continue to work while this is flow is running. This currently does not support resizing (you have to run the flow again with new sizes) or reloading the androidx.glance.state.GlanceStateDefinition state value.
Note: In order to handle lambda actions correctly, only one instance of runComposition for a particular id may run at the same time. Calling runComposition concurrently with the same ID will succeed, but the first call will resume with an exception.
If you need to call runComposition concurrently, you can omit id so that a random fake ID will be used. Otherwise, call runComposition sequentially when using the same id.
By default, this function uses UnmanagedSessionReceiver as the lambdaReceiver target to receive any lambda actions while this function is running. If you need to run this function in a non-default process, you can declare a sub-class of UnmanagedSessionReceiver in that process and pass its ComponentName here.
provideContent
suspend fun GlanceAppWidget.provideContent(
content: @Composable @GlanceComposable () -> Unit
): Nothing
Provides content to the Glance host, suspending until the Glance session is shut down.
If this function is called concurrently with itself, the previous call will throw CancellationException and the new content will replace it. This function should only be called from GlanceAppWidget.provideGlance.
TODO: make this a protected member once b/206013293 is fixed.
updateAll
suspend fun GlanceAppWidget.updateAll(context: Context): Unit
Update all App Widgets managed by the GlanceAppWidget class.
updateIf
suspend inline fun <State : Any?> GlanceAppWidget.updateIf(
context: Context,
predicate: (State) -> Boolean
): Unit
Update all App Widgets managed by the GlanceAppWidget class, if they fulfill some condition.
getAppWidgetState
suspend fun <T : Any?> GlanceAppWidget.getAppWidgetState(
context: Context,
glanceId: GlanceId
): T
Get the state of an App Widget.