I am trying to fetch some latest movie images and display them in the Android app widgets, As per the requirement, the widget should refresh every hour to get the latest movies images.
Since I am using coroutines throughout the app, I need to use the existing retrofit services with suspend function to get the data.
As per the Android documentation, it's safe to do heavy operations inside onDataSetChanged of RemoteViewsFactory So I am using this method itself to fetch data from service instead of WorkManger as it has its own drawbacks. See here (https://commonsware.com/blog/2018/11/24/workmanager-app-widgets-side-effects.html)
Called when notifyDataSetChanged() is triggered on the remote adapter. This allows a RemoteViewsFactory to respond to data changes by updating any internal references. Note: expensive tasks can be safely performed synchronously within this method. In the interim, the old data will be displayed within the widget.
In the Below code snippet, I am using runBlocking() to make a blocking retrofit call... Otherwise get view will be triggered before the data is fetched. Is this safe?
Note:Also the adapter is noticed in the onUpdate method of MyWidgetProvider every hour
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
....
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.listViewMovies)
.....
}
Code Sample: MyRemoteViewsFactory : RemoteViewsFactory {
override fun onDataSetChanged() {
try {
fetchFromServer()
} catch (e: Exception) {
clear()
}
}
private fun fetchFromServer() = runBlocking {
job = launch {
service.getMovieList?.also {
data = it
}
}
}
private fun clear(){
job?.cancel()
job = null
data = null
log("Cleared")
}
override fun onDestroy() {
clear()
}
Console log shows onDestory() s being called... but in different binder.
onCreate _main
1 _Binder:11940_5
2 _Binder:11940_5
3 _Binder:11940_5
4 _Binder:11940_5
5 _Binder:11940_5
6 _Binder:11940_5
7 _Binder:11940_5
8 _Binder:11940_5
9 _Binder:11940_5
10 _Binder:11940_5
11 _Binder:11940_5
onDestroy _Binder:11940_2
Clear called _Binder:11940_2
So My queries:
- Is it safe to use runBlocking inside onDataSetChanged Method?
- Is it safe to create nested coroutine inside runBlocking to cancel the Coroutines? is there any better way?
- Is it safe to fetch the data from the network inside onDataSetChanged().
- I can't make async call inside onDataSetChanged() .. So is there anyway we can handle without using runBlocking()
NOTE: This question specially For Android App Widget RemoteViewsFactory onDataSetChanged() implementation.
The documentation specifically says that "expensive tasks can be safely performed synchronously within this method. In the interim, the old data will be displayed within the widget."
So the answer to questions 1 and 3 is yes, it's ok to use
runBlocking
there and to make a network call.There might not be a real reason to do that, because
runBlocking
blocks the thread until the child coroutines are done, so you wouldn't give control back to the caller anyway until the work is finished. See next question.If you want to launch coroutines that you can later cancel (in case of concurrent use of
onDataSetChanged
andclear
- which is yet to be confirmed), you should instead declare aCoroutineScope
(as a property of your class) and launch coroutines using that scope instead of usingrunBlocking
, and then cancel the scope when/where appropriate.