How to update Glance App Widget inside a service in android or outside of the GlanceAppWidget

Viewed 436

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()
                }
            }
        }
    }
2 Answers

To update the widget, you can pass the variable in your GlanceWidget constructor:

class TestGlanceWidget(val state: WidgetState) : GlanceAppWidget() {

then you can call one of the update functions on the GlanceWidget:

val state = WidgetState()
TestGlanceWidget(state = state).updateAll(context)
Related