I have a ContentProvider
from a main app. The content will be shared with a consumer app. This consumer app has an app widget. I have tested the ContentProvider
and ContentObserver
to this consumer app in its Activity and all is well (meaning the RecyclerView
of the Activity is updated whenever an update from the main app triggers changes to the database). However, registering the ContentObserver
inside my AppWidgetProvider
does not work as expected.
My AppWidgetProvider
has the following code.
class StackWidgetProvider : AppWidgetProvider() {
override fun onEnabled(context: Context) {
Timber.i("Enabled")
if (favoriteUserProviderObserver == null) {
val appWidgetManager = AppWidgetManager.getInstance(context)
val componentName = ComponentName(context, StackWidgetProvider::class.java)
favoriteUserProviderObserver = FavoriteUserProviderObserver(appWidgetManager, componentName).let {
context.contentResolver.registerContentObserver(CONTENT_URI, true, it)
it
}
}
}
override fun onDisabled(context: Context) {
Timber.i("Disabled")
favoriteUserProviderObserver?.let {
context.contentResolver.unregisterContentObserver(it)
}
favoriteUserProviderObserver = null
}
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
for (appWidgetId in appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId)
}
}
....
companion object {
private var favoriteUserProviderObserver: FavoriteUserProviderObserver? = null
private fun updateAppWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int
) {
val intent = Intent(context, StackWidgetService::class.java).apply {
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
data = toUri(Intent.URI_INTENT_SCHEME).toUri()
}
val views = RemoteViews(context.packageName, R.layout.widget_favorite_user_stack).apply {
setRemoteAdapter(R.id.widget_favorite_user_stack_view, intent)
setEmptyView(R.id.widget_favorite_user_stack_view, R.id.widget_favorite_user_empty)
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}
I created a simple custom ContentObserver
class like below.
class FavoriteUserProviderObserver(
private val appWidgetManager: AppWidgetManager,
private val componentName: ComponentName
) : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
Timber.i("Provider observer triggered")
appWidgetManager.notifyAppWidgetViewDataChanged(
appWidgetManager.getAppWidgetIds(componentName), R.id.widget_favorite_user_stack_view
)
}
}
The above observer class is never triggered (even when I change the data in my main app). For further clarity, here's the code for my RemoteViewsService
and its factory.
class StackWidgetService : RemoteViewsService() {
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory =
StackRemoteViewsFactory(this.applicationContext)
}
class StackRemoteViewsFactory(private val context: Context) :
RemoteViewsService.RemoteViewsFactory {
private var widgetItems = listOf<UserProfileSummary>()
private lateinit var repository: FavoriteUserRepository
override fun onCreate() {
repository = FavoriteUserRepository(
FavoriteUserDataSource(context.contentResolver),
Dispatchers.IO
)
}
override fun onDataSetChanged() {
GlobalScope.launch {
widgetItems = repository.favoriteUsers().toList() // Tested; working on the Activity scope of the consumer app
Timber.i(widgetItems.toString())
}
}
override fun getViewAt(position: Int): RemoteViews =
RemoteViews(context.packageName, R.layout.widget_favorite_user_item).apply {
setTextViewText(R.id.widget_favorite_user_item_text, widgetItems[position].username)
}
override fun getLoadingView(): RemoteViews? = null
override fun getItemId(position: Int): Long = 0
override fun hasStableIds(): Boolean = false
override fun getCount(): Int {
return widgetItems.size
}
override fun getViewTypeCount(): Int = 1
override fun onDestroy() {}
}
So the logic is to ask the ContentObserver
to observe changes in the ContentProvider
. The observer is registered on the onEnabled
and onDisabled
part of the AppWidgetProvider
. Once the observer notices a change in ContentProvider
, it will ask the AppWidgetProvider
to update itself, thus calling onDataSetChanged
and fetching a new list of data.
However, the observer is never called. What could be the reason it's not working as expected here? (It can't be because of a lack of permission, because the Activity part of the consumer app is able to fetch the data just fine.)
What could be the reason it's not working as expected here?
An AppWidgetProvider
is a subclass of BroadcastReceiver
. Your instance of AppWidgetProvider
will live for (hopefully) a very short time, best measured in milliseconds. Basically, you get one onUpdate()
call (or other callback), and that instance is thrown away. The next callback gets a new instance.
As a result, doing anything in an AppWidgetProvider
that requires it to be around for a period of time is doomed.
The most likely solution, taking modern versions of Android into account, is to adopt more of a push solution. Bear in mind that any of your code can update the RemoteViews
for an app widget, simply by working with AppWidgetManager
. So, some code that is already running and knows about the data updates needs to push a new RemoteViews
, rather than expecting your AppWidgetProvider
to be able to react to changes.