Skip to content

Instantly share code, notes, and snippets.

@kishan-vadoliya
Last active December 30, 2023 12:45
Show Gist options
  • Save kishan-vadoliya/229b3f61c6408ebdeb98f15c52bb8567 to your computer and use it in GitHub Desktop.
Save kishan-vadoliya/229b3f61c6408ebdeb98f15c52bb8567 to your computer and use it in GitHub Desktop.
Android App Widget Development with Glance
// https://medium.com/atlas/android-app-widget-development-with-glance-532a5a8d602c
// User Interface
<!-- API 31 (Android 12 and up) -->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/widget_description"
android:minWidth="200dp"
android:minHeight="200dp"
android:resizeMode="horizontal|vertical"
android:previewImage="@drawable/calendar_widget_preview_image"
android:previewLayout="@layout/calendar_widget_small_layout"
android:targetCellWidth="2"
android:targetCellHeight="3"
android:maxResizeWidth="300dp"
android:maxResizeHeight="600dp"
android:widgetCategory="home_screen"
android:updatePeriodMillis="0"
/>
Column(
modifier = GlanceModifier.padding(all = 2.dp).fillMaxWidth().background(Color.White),
horizontalAlignment = Alignment.CenterHorizontally
) {
// UP/Prev button
Image(
provider = ImageProvider(com.bottlerocket.android.consolidator.R.drawable.ic_arrow_up_24),
contentDescription = "Back",
modifier = GlanceModifier.defaultWeight()
.background(Color.Transparent)
.clickable(onClick = actionRunCallback<CalendarActionCallback>(
actionParametersOf(pairs = arrayOf(ActionParameters.Key<DateArrow>(KEY_DATE_ARROW) to DateArrow.Back)))
)
)
val monthEvents = calendarEventsMap.filter { it.key.year == firstMonthState.year && it.key.month == firstMonthState.month }
MonthCalendarWidgetUI(firstMonthState, monthEvents)
if (numMonthsToShow >= 2) {
val secondMonth = firstMonthState.plusMonths(1)
val secondMonthEvents = calendarEventsMap.filter { it.key.year == secondMonth.year && it.key.month == secondMonth.month }
MonthCalendarWidgetUI(secondMonth, secondMonthEvents)
}
}
// Work Manager
fun startCalendarDataFetch(workManager: WorkManager) {
calendarWidgetLog("queueCalendarDataRequest")
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresCharging(false)
.setRequiresBatteryNotLow(true)
.build()
// NOTE: min period is 15 minutes.
val request = PeriodicWorkRequestBuilder<CalendarWorkManager>(
Duration.ofMinutes(
CALENDAR_WORK_REQ_PERIOD_MINUTES
))
.setConstraints(constraints)
.setInitialDelay(Duration.ofSeconds(CALENDAR_WORK_REQ_INITIAL_DELAY_SECONDS))
.build()
// MUST: Specifying "unique" and "KEEP" will ensure there is only one worker in the system.
val operation = workManager.enqueueUniquePeriodicWork(
CALENDAR_FETCH_WORKER,
ExistingPeriodicWorkPolicy.KEEP,
request)
calendarWidgetLog("WorkManager queue status: ${operation.result}")
}
//
private suspend fun updateWidgetData(events: List<DayEvent>) {
GlanceAppWidgetManager(context = context).getGlanceIds(ConsolidatorAppWidget::class.java).forEach { glanceId ->
updateAppWidgetState(context, glanceId) { prefs ->
calendarWidgetLog("updateAppWidgetState: glanceId: $glanceId")
// Each app widget instance stores in a different DataStore file.
prefs.setCalendarData(events)
}
// VERY IMPORTANT: Refreshing App Widget with updated events here.
calendarWidgetLog("updateWidgetData: Update UI")
ConsolidatorAppWidget().update(context, glanceId)
}
}
//
class ConsolidatorAppWidget: GlanceAppWidget() {
private val isDebugOverride = false // Set to true to have only one widget size to reduce logging clutter
/**
* Used to dynamically configure Widget as user is resizing it.
*/
override val sizeMode: SizeMode = SizeMode.Responsive(
if (isDebugOverride && isDebug()) setOf(ONE_MONTH_WIDGET) else setOf(ONE_MONTH_WIDGET, TWO_MONTH_WIDGET)
)
...
}
// Useful Links
General App Widget Documentation (Non-Glance specific)
Glance Overview
Glance Sample Code from Google
StackOverflow topic/tag: glance-appwidget
Glance Bugs List
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment