Android 12 introduced new API for creating app widgets called Glance. My use case is, I have a button inside a Glance composable and upon pressing that a foreground service is invoked which fetches data from server and update the widget. In legacy API we can do update by creating new remote views and update the widget via app widget manager like below.
class COVIDSummaryUpdateService : Service() {
private val TAG = "COVIDSummaryUpdateService"
private val FOREGROUND_SERVICE_ID = 111
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startNotificationForForeground()
val views = RemoteViews(
this@COVIDSummaryUpdateService.packageName,
R.layout.covid_summary_layout
)
views.setViewVisibility(R.id.ivRefresh, View.GONE)
views.setViewVisibility(R.id.progressBar, View.VISIBLE)
val componentName = ComponentName(
this@COVIDSummaryUpdateService.applicationContext,
COVIDSummaryAppWidgetProvider::class.java
)
val manager =
AppWidgetManager.getInstance(this@COVIDSummaryUpdateService.applicationContext)
manager.updateAppWidget(componentName, views)
COVIDApiService.getCOVIDApi().getSummary()
.enqueue(object : Callback<SummaryResponse> {
override fun onResponse(
call: Call<SummaryResponse>,
response: Response<SummaryResponse>
) {
if (response.isSuccessful && response.code() == 200) {
updateWidgetWithResponse(response.body())
} else {
hideProgressView()
}
stopService()
}
override fun onFailure(call: Call<SummaryResponse>, t: Throwable) {
t.message?.let { Log.d(TAG, it) }
hideProgressView()
stopService()
}
})
return START_STICKY
}
private fun stopService() {
stopForeground(true)
stopSelf()
}
private fun updateWidgetWithResponse(summaryResponse: SummaryResponse?) {
try {
summaryResponse?.let {
val views = RemoteViews(
this@COVIDSummaryUpdateService.packageName,
R.layout.covid_summary_layout
)
views.setTextViewText(
R.id.tvNewConfirmedCases,
"${summaryResponse.global.newConfirmed}"
)
views.setTextViewText(
R.id.tvNewDeaths,
"${summaryResponse.global.newDeaths}"
)
views.setTextViewText(
R.id.tvNewRecovered,
"${summaryResponse.global.newRecovered}"
)
views.setTextViewText(
R.id.tvTotalConfirmedCases,
"${summaryResponse.global.totalConfirmed}"
)
views.setTextViewText(
R.id.tvTotalDeaths,
"${summaryResponse.global.totalDeaths}"
)
views.setTextViewText(
R.id.tvTotalRecovered,
"${summaryResponse.global.totalRecovered}"
)
views.setViewVisibility(R.id.ivRefresh, View.VISIBLE)
views.setViewVisibility(R.id.progressBar, View.GONE)
val componentName = ComponentName(
this@COVIDSummaryUpdateService.applicationContext,
COVIDSummaryAppWidgetProvider::class.java
)
val manager =
AppWidgetManager.getInstance(this@COVIDSummaryUpdateService.applicationContext)
manager.updateAppWidget(componentName, views)
}
} catch (e: Exception) {
e.message?.let { Log.d(TAG, it) }
hideProgressView()
stopService()
}
}
private fun hideProgressView() {
val views = RemoteViews(
this@COVIDSummaryUpdateService.packageName,
R.layout.covid_summary_layout
)
views.setViewVisibility(R.id.ivRefresh, View.VISIBLE)
views.setViewVisibility(R.id.progressBar, View.GONE)
val componentName = ComponentName(
this@COVIDSummaryUpdateService.applicationContext,
COVIDSummaryAppWidgetProvider::class.java
)
val manager =
AppWidgetManager.getInstance(this@COVIDSummaryUpdateService.applicationContext)
manager.updateAppWidget(componentName, views)
}
private fun startNotificationForForeground() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForeground(
FOREGROUND_SERVICE_ID,
createNotification(
"COVID19Service", "COVID19 Summary Channel",
null,
getString(R.string.foreground_not_text)
)
)
}
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotification(
channelId: String,
channelName: String,
contentTitle: String?,
contentText: String,
pendingIntent: PendingIntent? = null
): Notification {
val notificationChannel =
NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE)
notificationChannel.description = channelId
notificationChannel.setSound(null, null)
notificationChannel.lightColor = Color.BLUE
notificationChannel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(notificationChannel)
return Notification.Builder(this, channelId).let { builder ->
contentTitle?.let {
builder.setContentTitle(contentTitle)
}
builder.setContentText(contentText)
builder.setSmallIcon(R.drawable.ic_covid_19_33)
pendingIntent?.let { builder.setContentIntent(it) }
builder.build()
}
}
}
}