androidkotlinandroid-widgetandroid-workmanagerglance-appwidget

Using CoroutineWorker with Android Widget in Jetpack Glance results in just a loading indicator


I have created an Android Widget using Jetpack Glance, I need to update it using CoroutineWorker so I am using WorkManager for it, here is the sample code

class MyWidget : GlanceAppWidget() {

    companion object {
        val KEY_TOPIC = stringPreferencesKey("topic")
        val KEY_QUOTE = stringPreferencesKey("quote")
    }

    override val sizeMode = SizeMode.Exact

   // override var stateDefinition: GlanceStateDefinition<*> = PreferencesGlanceStateDefinition

    override suspend fun provideGlance(context: Context, id: GlanceId) {

        provideContent {

            val displayText = currentState(KEY_QUOTE) ?: "Quote not found"
            val topic = currentState(KEY_TOPIC) ?: ""

            GlanceTheme {
                Scaffold(
                    titleBar = {
                        TitleBar(startIcon = ImageProvider(R.mipmap.ic_launcher), title = "Hello")
                    }, backgroundColor = GlanceTheme.colors.widgetBackground
                ) {
                    Column(
                        modifier = GlanceModifier.background(color = Color.Red)
                            .padding(30.dp)
                    ) {
                        Text(
                            text = "Display Text is $displayText && Topic is $topic",
                            style = TextStyle(
                                color = ColorProvider(
                                    color = Color(0xFF000000)
                                ),
                                fontSize = 12.sp,
                                fontWeight = FontWeight.Medium
                            ),
                            modifier = GlanceModifier.clickable {
                                actionStartActivity(activity = MainActivity::class.java)
                            }
                        )

                        Button(
                            "Start Activity", onClick = actionStartActivity<MainActivity>(),
                            style = TextStyle(
                                color = ColorProvider(
                                    color = Color(0x00FF0000)
                                ),
                                fontSize = 12.sp,
                                fontWeight = FontWeight.Medium
                            ),
                        )
                    }
                }


            }
        }
    }


    override fun onCompositionError(
        context: Context,
        glanceId: GlanceId,
        appWidgetId: Int,
        throwable: Throwable
    ) {
        super.onCompositionError(context, glanceId, appWidgetId, throwable)
        val remoteView = RemoteViews(context.packageName, R.layout.custom_error_layout)
        Log.i("errorMessageis", "${throwable.message} ${throwable.localizedMessage}")
        remoteView.setTextViewText(
            R.id.textview,
            "${throwable.message} ${throwable.localizedMessage}"
        )
        AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, remoteView)
    }


}


@HiltWorker
class CustomWorker @AssistedInject constructor(
    @Assisted val context: Context,
    @Assisted workerParameters: WorkerParameters,
) : CoroutineWorker(context, workerParameters) {

    override suspend fun doWork(): Result {

        CoroutineScope(Dispatchers.IO).launch {
            val currentTimeMillis = System.currentTimeMillis()
            val date = Date(currentTimeMillis)
            val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
            val formattedDate = sdf.format(date)

            val glanceIds =
                GlanceAppWidgetManager(context).getGlanceIds(MyWidget::class.java)
            glanceIds.forEach { id ->
                updateAppWidgetState(context, id) { prefs ->
                    prefs[KEY_QUOTE] = currentTimeMillis.toString()
                    prefs[KEY_TOPIC] = formattedDate
                }
                MyWidget().update(context, id)
            }
        }
        return Result.success()
    }
}

Now when I try to create widget from my installed android app, it just shows the loading screen which is created from android:initialLayout="@layout/glance_default_loading_layout"

Here is the complete source code I don't see any error messages as such


Solution

  • I tried to run the app and it got stuck in an endless loop of starting workers when you create a widget. Apparently it was caused by the way you create WorkerFactory or rather that it should be HiltWorkerFactory. Refactoring MyApplication seemingly fixed it:

    @HiltAndroidApp
    class MyApplication : Application(), Configuration.Provider {
    
        @Inject
        lateinit var workerFactory: HiltWorkerFactory
    
        override val workManagerConfiguration: Configuration by lazy {
            Configuration.Builder()
                .setMinimumLoggingLevel(Log.DEBUG)
                .setWorkerFactory(workerFactory)
                .build()
        }
    }