Search code examples
androidglance-appwidgetglance

In Android, Glance widgets are "flickering" during every update (even if there is no content change)


I'm not sure if this is a problem with Glance still being in alpha, or if I'm doing something wrong. But every time the widget receiver's onUpdate() is triggered, it's being completely re-composed. It resets to the initialLayout for a second, and then re-composes to the correct state.

The problem is that it happens even if there is no change in the widget state, so it just "flickers" like that every time there's a widget update, and it looks really bad.

I've already implemented manual updates using MyWidget().updateIf<Preferences>, so that my app only updates the widget when the state changes, but there are still automatic updates that the OS is doing, so the "flickering" is still happening.

EDIT: After some more testing, I found that this isn't actually happening when calling the GlanceAppWidget update() method. In fact, I removed all calls to the GlanceAppWidget and GlanceAppWidgetManager from my app. However, the "flickering" is still happening any time the widget gets updated (automatically, triggered by the OS).

I've tried disabling the widget refresh in the XML by setting the updatePeriodMillis to both 0 and 86400000, but that doesn't seem to work. I've also tried removing the updatePeriodMillis from the XML.

So, it appears that the flicker happens any time the GlanceAppWidget has it's Content() function called, regardless of what's actually triggering that call. Just for reference, here's the basic Kotlin class for the widget:

class WidgetSimple : GlanceAppWidget() {

    override val sizeMode: SizeMode = SizeMode.Single

    @Composable
    override fun Content() {

        // code to actually draw the components
        // no matter what's here, the widget will flicker
        // even if we leave it blank, it'll still flicker between the preview layout and a blank screen
    }
}

class WidgetSimpleReceiver : GlanceAppWidgetReceiver() {
    override val glanceAppWidget: GlanceAppWidget = WidgetSimple()
}

Solution

  • After posting a bug on Google's Issue Tracker, and doing a bunch of troubleshooting to figure out what was happening, I found out that it's a problem with WorkManager, and not with Glance or anything else widget-related. Simply enqueueing any work would cause the widgets to flicker because of the way that WorkManager behaves.

    However, there's a workaround solution that was provided in that Issue Tracker thread, and it is to to create a one-time Worker set to 10 years out, (use setInitialDelay) and set at least one constraint on it.

    This ensures that the WorkManager component is not disabled and an app widget update is not triggered inadvertently. You can use ExistingWorkPolicy.KEEP to ensure that subsequent enqueues no-op

    Basically, I had to create a dummy CoroutineWorker class that doesn't actually do anything inside doWork():

    class DelayedWidgetWorker(
        appContext: Context,
        workerParams: WorkerParameters,
    ): CoroutineWorkerCompat(appContext, workerParams) {
    
        companion object{
            const val TAG = "appWidgetWorkerKeepEnabled"
        }
    
        override suspend fun doWork(): Result {
            Logger.d("Dummy DelayedWidgetWorker")
            return Result.success()
        }
    }
    

    and then call this function before every widget update:

            WorkManager.getInstance(context).enqueueUniqueWork(
                DelayedWidgetWorker.TAG,
                ExistingWorkPolicy.KEEP,
                OneTimeWorkRequestBuilder<DelayedWidgetWorker>()
                    .setInitialDelay(10 * 365, TimeUnit.DAYS)
                    .setConstraints(
                        Constraints.Builder()
                            .setRequiresCharging(true)
                            .build()
                    )
                    .build()
            )
    

    This ensures that there's always a single WorkManager task that's in the queue 10 years in the future, which prevents it from causing the "flickering"