I'm making an android widget to list some items that I can increment and decrement the count.
It worked fine, but after a few minutes (maybe when all services stop) if when I click on the buttons, the list becomes empty and displays the view of empty list.
my remote view factory:
class GoalWidgetDataProvider(
private val context: Context,
private val intent: Intent?
) : RemoteViewsFactory {
override fun onCreate() { }
override fun onDataSetChanged() {
if (GoalsRepository.goals.isNotEmpty()) {
getViewAt(0)
}
}
override fun onDestroy() { }
override fun getCount(): Int {
return GoalsRepository.goals.size
}
override fun getViewAt(position: Int): RemoteViews {
val fillIntentAdd = Intent()
fillIntentAdd.putExtra(GoalWidget.UPDATE_TYPE,GoalWidget.INCREMENT)
fillIntentAdd.putExtra(GoalWidget.NAME_KEY, GoalsRepository.goals[position].name)
fillIntentAdd.putExtra(GoalWidget.MULTIPLIER, 1)
val fillIntentSub = Intent()
fillIntentSub.putExtra(GoalWidget.UPDATE_TYPE,GoalWidget.DECREMENT)
fillIntentSub.putExtra(GoalWidget.NAME_KEY, GoalsRepository.goals[position].name)
fillIntentSub.putExtra(GoalWidget.MULTIPLIER, -1)
val remoteViews = RemoteViews(context.packageName, R.layout.goal_item_widget)
remoteViews.setTextViewText(R.id.name, GoalsRepository.goals[position].name)
remoteViews.setTextViewText(R.id.count_text, GoalsRepository.goals[position].count.toString())
remoteViews.setTextColor(R.id.count_text, GoalsRepository.goals[position].fgColor)
remoteViews.setTextColor(R.id.name, GoalsRepository.goals[position].fgColor)
remoteViews.setInt(R.id.btn_increment, "setColorFilter", GoalsRepository.goals[position].fgColor)
remoteViews.setInt(R.id.btn_decrement, "setColorFilter", GoalsRepository.goals[position].fgColor)
remoteViews.setInt(R.id.icon, "setColorFilter", GoalsRepository.goals[position].fgColor)
remoteViews.setInt(R.id.image_bg, "setColorFilter", GoalsRepository.goals[position].bgColor)
remoteViews.setOnClickFillInIntent(R.id.btn_increment, fillIntentAdd)
remoteViews.setOnClickFillInIntent(R.id.btn_decrement, fillIntentSub)
return remoteViews
}
override fun getLoadingView(): RemoteViews? = null
override fun getViewTypeCount(): Int = 1
override fun getItemId(i: Int): Long = i.toLong()
override fun hasStableIds(): Boolean = true
}
AppWidgetProvider:
class GoalWidget : AppWidgetProvider() {
companion object {
private const val ACTION_INCREMENT = "INTENT_INCREMENT"
const val UPDATE_TYPE = "UPDATE_TYPE"
const val NAME_KEY = "NAME_KEY"
const val INCREMENT = "INCREMENT"
const val DECREMENT = "DECREMENT"
const val MULTIPLIER = "MULTIPLIER"
}
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
val widgetManager = AppWidgetManager.getInstance(context.applicationContext)
widgetManager.notifyAppWidgetViewDataChanged(widgetManager.getAppWidgetIds(ComponentName(context.applicationContext.packageName, GoalWidget::class.java.name)), R.id.widget_listView)
for (appWidgetId in appWidgetIds) {
val addIntent = Intent(context, javaClass)
addIntent.action = ACTION_INCREMENT
val addPendingIntent = PendingIntent.getBroadcast(context, appWidgetId, addIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
val serviceIntent = Intent(context, GoalWidgetService::class.java)
serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
serviceIntent.data = Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME))
val intent = Intent(context, GoalsActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val views = RemoteViews(context.packageName, R.layout.goal_widget)
views.setOnClickPendingIntent(R.id.layout_root, pendingIntent)
views.setRemoteAdapter(R.id.widget_listView, serviceIntent)
views.setEmptyView(R.id.widget_listView, R.id.goal_widget_empty)
views.setPendingIntentTemplate(R.id.widget_listView, addPendingIntent)
appWidgetManager.updateAppWidget(appWidgetId, views)
}
super.onUpdate(context, appWidgetManager, appWidgetIds)
}
override fun onReceive(context: Context?, intent: Intent?) {
if (intent != null) {
when (intent.action) {
ACTION_INCREMENT -> {
val curName = intent.getStringExtra(NAME_KEY) ?: ""
when (intent.extras?.getString(UPDATE_TYPE,"")) {
INCREMENT, DECREMENT -> {
val multi = intent.getIntExtra(MULTIPLIER, 1)
CoroutineScope(Dispatchers.Main).launch {
GoalsRepository.incrementWidget(curName, multi)
val widgetManager =
AppWidgetManager.getInstance(context!!.applicationContext)
widgetManager.notifyAppWidgetViewDataChanged(
widgetManager.getAppWidgetIds(
ComponentName(
context.applicationContext.packageName,
GoalWidget::class.java.name
)
), R.id.widget_listView
)
}
}
}
}
}
}
super.onReceive(context, intent)
}
override fun onDeleted(context: Context, appWidgetIds: IntArray) { }
override fun onEnabled(context: Context) { }
override fun onDisabled(context: Context) { }
}
and my repository:
object GoalsRepository {
var goalsDao : GoalDao? = null
set(value) {
field = value
loadGoals()
}
private var goalsList : ArrayList<GoalItem> = ArrayList()
val goals : List<GoalItem> = goalsList
fun setGoal(goal : GoalItem, old : GoalItem?) {
if (old == null) {
goalsList.add(goal)
CoroutineScope(Dispatchers.IO).launch {
goalsDao?.insert(goal)?.let {
goal.id = it[0]
}
}
} else {
val idx = goalsList.indexOf(old)
goalsList[idx] = goal
CoroutineScope(Dispatchers.IO).launch {
goalsDao?.update(goal)
}
}
}
fun update(idx : Int, goal : GoalItem) {
goalsList[idx] = goal
CoroutineScope(Dispatchers.IO).launch {
goalsDao?.update(goal)
}
}
fun incrementWidget(name : String, inc : Int) {
goalsList.find { it.name == name }?.let { goal ->
val idx = goalsList.indexOf(goal)
update(
idx,
goal.copy(count = goal.count.plus(goal.increment * inc))
)
}
}
fun deleteGoal(goal : GoalItem) {
goalsList.remove(goal)
CoroutineScope(Dispatchers.IO).launch {
goalsDao?.delete(goal)
}
}
private fun loadGoals() {
goalsDao?.getAllGoals()?.let {
goalsList.addAll(it)
}
}
}
I don't know exactly why, but using shared preferences instead of Rom Database worked fine.
I think it's because the database initialization take a time and the widget does not support asyncronous tasks.
i didn't find an explanation, but it worked.