Search code examples
androidkotlinandroid-pendingintent

How do I correctly update a home screen widget using a refresh button? (Android - Kotlin)


I have created a home screen widget that will display the current price of Bitcoin with a refresh button for the user to manually fetch the price data. I'm using Okhttp for the HTTP request and that part works just as I need.

However, when adding the widget to my home screen, it only responds to the button press some times. I can visually see the button being pressed but nothing is happening. Other times, it works flawlessly. I'm curious what could be causing this behavior?

Here is my onUpdate() function for my AppWidgetProvider class.

override fun onUpdate(
    context: Context,
    appWidgetManager: AppWidgetManager,
    appWidgetIds: IntArray
) {
    // There may be multiple widgets active, so update all of them
    for (appWidgetId in appWidgetIds) {
        
        val intent = Intent(context, PriceWidget::class.java)
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)

        val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0)
        val views = RemoteViews(context.packageName, R.layout.price_widget)

        views.setOnClickPendingIntent(R.id.refresh_button, pendingIntent)

        appWidgetManager.updateAppWidget(appWidgetId, views)
    }
}

When the button is pressed, it creates a PendingIntent which calls the AppWidgetProvider's function onReceive():

override fun onReceive(context: Context?, intent: Intent?) {
    super.onReceive(context, intent)
    println("Refresh button pressed!")

    if (intent!!.extras != null) {
        val appWidgetManager = AppWidgetManager.getInstance(context)
        val thisAppWidget = ComponentName(context!!.packageName, PriceWidget::class.java.name)
        val appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget)
        for (appWidgetId in appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId)
        }
    }

This is my updateAppWidget function:

internal fun updateAppWidget(
    context: Context,
    appWidgetManager: AppWidgetManager,
    appWidgetId: Int
) {

    val views = RemoteViews(context.packageName, R.layout.price_widget)
    views.setTextViewText(R.id.appwidget_text, "Loading...")
    views.setTextViewText(R.id.price_change_widget, "24h: Loading...")
    appWidgetManager.updateAppWidget(appWidgetId, views)

    getWidgetData(views, appWidgetManager, appWidgetId)

}

And lastly, my function getWidgetData makes the HTTP GET request. On response, the JSON string is converted to a Data() object using Gson. This code changes the text using a RemoteView and calls the appWidhetManager.updateAppWidget function.

// sets textView to current price
views.setTextViewText(R.id.appwidget_text, data.price())
views.setTextViewText(R.id.price_change_widget, data.change24h())

// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views)

I have followed other StackOverflow answers and none seem to fix this issue. I have noticed that adding two of the widget to the home screen solves the issue only until one of the widgets is deleted.

Open to all suggestions and/or help of any kind! Let me know if there's any other info I can provide!


Solution

  • October 27, 2021 -

    I found a solution that works to refresh the widget with new information.

    I removed the login from the onUpdate() method in the AppWidgetProvider so now it just has the boilerplate code:

     override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        // There may be multiple widgets active, so update all of them
        for (appWidgetId in appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId)
        }
    }
    

    Now, the updateAppWidget() method has all the logic. I create a RemoteView() linked to the layout of the homescreen widget, modify some of the text on the widget using setTextViewText(), and then I call appWidgetManager.updateAppWidget(appWidgetId, views).

    In the same function, I continue the logic by creating a PendingIntent() and when the refresh button is pressed I call a separate function to handle the HTTP GET request.

    Once the data has been returned and processed, I call appWidgetManager.updateAppWidget(appWidgetId, views) again. Since this happens from a separate function, I had to pass in appWidgetManager and appWidgetId as variables.

    I hope this can help someone out. The button refresh works basically 99% of the time now. It's rare that it doesn't but I think that has to do with Low Power Mode on Android phones. Here's the source code from my GitHub: PriceWidget.kt