I would like to load a list of cars from the internet into a ListView in a Widget. When I am requesting the cars in the onDatasetChanged method, it does not update the list. When I however manually click my refresh button, it does refresh it. What could be the problem? In order to provide as much information as possible, I put the complete source into a ZIP file, which can be unzipped and be opened in Android Studio. I will embed it down below too. Note: I am using RxJava for the async requests and the code is written in Kotlin, although the question is not per sé language bound.
CarsWidgetProvider
class CarsWidgetProvider : AppWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
Log.d("CAR_LOG", "Updating")
val widgets = appWidgetIds.size
for (i in 0 until widgets) {
Log.d("CAR_LOG", "Updating number $i")
val appWidgetId = appWidgetIds[i]
// Get the layout
val views = RemoteViews(context.packageName, R.layout.widget_layout)
val otherIntent = Intent(context, ListViewWidgetService::class.java)
views.setRemoteAdapter(R.id.cars_list, otherIntent)
// Set an onclick listener for the action and the refresh button
views.setPendingIntentTemplate(R.id.cars_list, getPendingSelfIntent(context, "action"))
views.setOnClickPendingIntent(R.id.refresh, getPendingSelfIntent(context, "refresh"))
// Tell the AppWidgetManager to perform an update on the current app widget
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
private fun onUpdate(context: Context) {
val appWidgetManager = AppWidgetManager.getInstance(context)
val thisAppWidgetComponentName = ComponentName(context.packageName, javaClass.name)
val appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidgetComponentName)
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.cars_list)
onUpdate(context, appWidgetManager, appWidgetIds)
}
private fun getPendingSelfIntent(context: Context, action: String): PendingIntent {
val intent = Intent(context, javaClass)
intent.action = action
return PendingIntent.getBroadcast(context, 0, intent, 0)
}
@SuppressLint("CheckResult")
override fun onReceive(context: Context?, intent: Intent?) {
super.onReceive(context, intent)
if (context != null) {
if ("action" == intent!!.action) {
val car = intent.getSerializableExtra("car") as Car
// Toggle car state
Api.toggleCar(car)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
Log.d("CAR_LOG", "We've got a new state: $it")
car.state = it
onUpdate(context)
}
} else if ("refresh" == intent.action) {
onUpdate(context)
}
}
}
}
ListViewWidgetService
class ListViewWidgetService : RemoteViewsService() {
override fun onGetViewFactory(intent: Intent): RemoteViewsService.RemoteViewsFactory {
return ListViewRemoteViewsFactory(this.applicationContext, intent)
}
}
internal class ListViewRemoteViewsFactory(private val context: Context, intent: Intent) : RemoteViewsService.RemoteViewsFactory {
private var cars: ArrayList<Car> = ArrayList()
override fun onCreate() {
Log.d("CAR_LOG", "Oncreate")
cars = ArrayList()
}
override fun getViewAt(position: Int): RemoteViews {
val remoteViews = RemoteViews(context.packageName, R.layout.widget_list_item_car)
// Get the current car
val car = cars[position]
Log.d("CAR_LOG", "Get view at $position")
Log.d("CAR_LOG", "Car: $car")
// Fill the list item with data
remoteViews.setTextViewText(R.id.title, car.title)
remoteViews.setTextViewText(R.id.description, car.model)
remoteViews.setTextViewText(R.id.action, "${car.state}")
remoteViews.setInt(R.id.action, "setBackgroundColor",
if (car.state == 1) context.getColor(R.color.colorGreen) else context.getColor(R.color.colorRed))
val extras = Bundle()
extras.putSerializable("car", car)
val fillInIntent = Intent()
fillInIntent.putExtras(extras)
remoteViews.setOnClickFillInIntent(R.id.activity_chooser_view_content, fillInIntent)
return remoteViews
}
override fun getCount(): Int {
Log.d("CAR_LOG", "Counting")
return cars.size
}
@SuppressLint("CheckResult")
override fun onDataSetChanged() {
Log.d("CAR_LOG", "Data set changed")
// Get a token
Api.getToken()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { token ->
if (token.isNotEmpty()) {
Log.d("CAR_LOG", "Retrieved token")
DataModel.token = token
// Get the cars
Api.getCars()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
cars = it as ArrayList<Car>
Log.d("CAR_LOG", "Found cars: $cars")
}
} else {
Api.getCars()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
cars = it as ArrayList<Car>
Log.d("CAR_LOG", "Found cars: $cars")
}
}
}
}
override fun getViewTypeCount(): Int {
Log.d("CAR_LOG", "Get view type count")
return 1
}
override fun getItemId(position: Int): Long {
Log.d("CAR_LOG", "Get item ID")
return position.toLong()
}
override fun onDestroy() {
Log.d("CAR_LOG", "On Destroy")
cars.clear()
}
override fun hasStableIds(): Boolean {
Log.d("CAR_LOG", "Has stable IDs")
return true
}
override fun getLoadingView(): RemoteViews? {
Log.d("CAR_LOG", "Get loading view")
return null
}
}
DataModel
object DataModel {
var token: String = ""
var currentCar: Car = Car()
var cars: ArrayList<Car> = ArrayList()
init {
Log.d("CAR_LOG", "Creating data model")
}
override fun toString(): String {
return "Token : $token \n currentCar: $currentCar \n Cars: $cars"
}
}
Car
class Car : Serializable {
var id : String = ""
var title: String = ""
var model: String = ""
var state: Int = 0
}
EDIT
After I received some answers, I started figuring out a temporary fix, which is calling .get() on an AsyncTask, which makes it synchronous
@SuppressLint("CheckResult")
override fun onDataSetChanged() {
cars = GetCars().execute().get() as ArrayList<Car>
}
class GetCars : AsyncTask<String, Void, List<Car>>() {
override fun doInBackground(vararg params: String?): List<Device> {
return Api.getCars().blockingFirst();
}
}