50

I have an android widget that fetches data from a server every 10 minutes and display's it on the screen.
I'd like to add a "Refresh" button to that widget.
When the user clicks that button I'd like to run the method that fetches the information from the server.
Adding an event handler to a button in an application is very easy, however I couldn't find an example for a widget.
I'd like to get some help with adding a function to a button click in a widget.

Sharon Haim Pour
  • 6,595
  • 12
  • 42
  • 64

7 Answers7

80

Here is one example more that should help:

package com.automatic.widget;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;

public class Widget extends AppWidgetProvider {

    private static final String SYNC_CLICKED    = "automaticWidgetSyncButtonClick";

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        RemoteViews remoteViews;
        ComponentName watchWidget;

        remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
        watchWidget = new ComponentName(context, Widget.class);

        remoteViews.setOnClickPendingIntent(R.id.sync_button, getPendingSelfIntent(context, SYNC_CLICKED));
        appWidgetManager.updateAppWidget(watchWidget, remoteViews);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO Auto-generated method stub
        super.onReceive(context, intent);

        if (SYNC_CLICKED.equals(intent.getAction())) {

            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

            RemoteViews remoteViews;
            ComponentName watchWidget;

            remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
            watchWidget = new ComponentName(context, Widget.class);

            remoteViews.setTextViewText(R.id.sync_button, "TESTING");

            appWidgetManager.updateAppWidget(watchWidget, remoteViews);

        }
    }

    protected PendingIntent getPendingSelfIntent(Context context, String action) {
        Intent intent = new Intent(context, getClass());
        intent.setAction(action);
        return PendingIntent.getBroadcast(context, 0, intent, 0);
    }
}
Erti-Chris Eelmaa
  • 25,338
  • 6
  • 61
  • 78
  • 2
    Thanks For Such Good Example I have use This code and my application Working fine....Thnks a Lots – Butani Vijay Jun 09 '13 at 12:22
  • 1
    This code worked without the confusion other examples presented - this is an excellent, concise example of the dev guide info - thanks! – headscratch Sep 10 '13 at 18:52
  • 6
    A note about `PendingIntent.getBroadcast(context, 0, intent, 0)`: This will cause a bug if you have more than one instance of widgets. Problem: only last widget gets updated whenever you click any instance of widget. Solution: pass widgetId to `getPendingSelfIntent` and try this: `PendingIntent.getBroadcast(context, widgetId, intent, 0)` [Detailed description](http://stackoverflow.com/a/13487623/1493081) – Alireza Mirian Jan 29 '16 at 20:04
49

I found out how to do that.
Add an action to the AndroidManifest.xml file in the > <receiver><intent-filter> tag:

<action android:name="MY_PACKAGE_NAME.WIDGET_BUTTON" />

In the provider add a constant that matches the action name:

public static String WIDGET_BUTTON = "MY_PACKAGE_NAME.WIDGET_BUTTON";

In the onUpdate() method add a pending intent that matches the action:

Intent intent = new Intent(WIDGET_BUTTON);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.MY_BUTTON_ID, pendingIntent );

Finally, in the onRecieve() method, check the action name:

 if (WIDGET_BUTTON.equals(intent.getAction())) {
//your code here

    }
Sharon Haim Pour
  • 6,595
  • 12
  • 42
  • 64
  • 1
    I think it is interesting to note that you can (and probably should) use an explicit intent instead of an implicit intent. This means that you don't have to define the action in the manifest and that you should create the intent like this: Intent intent = new Intent(context, MyClass.class); – user936580 Mar 08 '14 at 09:19
  • Thanks! This button event listener for widget helps me a lot. Now, it's time for experiment and I have to explore more about widget making for Android. – David Dimalanta Dec 15 '14 at 04:34
  • This code is a better option than the one above as it uses explicit intent which makes it easier to call the intent from a configuration activity too – TanmayP Feb 16 '15 at 00:25
  • What is this? `views` – Yousha Aleayoub Sep 02 '16 at 17:52
  • Why has the packagename to be prefix? – XxGoliathusxX Oct 26 '16 at 01:33
12

Here is another answer with the following benefits:

  • It handles all App Widget instances (a user might have multiple instances of your widget in various configurations/sizes on your screen). Coding for all instances is what the official documentation prescribes. See Guide > App Widgets > Using the AppWidgetProvider Class , scroll down to the code example for "ExampleAppWidgetProvider".
  • The workhorse code in onReceive in effect calls onUpdate (so you reduce code duplication).
  • The code in onUpdate(Context context) is generalised so that it can be dropped into any AppWidgetProvider subclass.

The code:

public class MyWidget extends AppWidgetProvider {

    private static final String ACTION_UPDATE_CLICK = 
              "com.example.myapp.action.UPDATE_CLICK";

    private static int mCount = 0;

    private static String getMessage() {
        return String.valueOf(mCount++);
    }

    private PendingIntent getPendingSelfIntent(Context context, String action) {
        // An explicit intent directed at the current class (the "self").
        Intent intent = new Intent(context, getClass());
        intent.setAction(action);
        return PendingIntent.getBroadcast(context, 0, intent, 0);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
                         int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);

        String message = getMessage();

        // Loop for every App Widget instance that belongs to this provider.
        // Noting, that is, a user might have multiple instances of the same
        // widget on
        // their home screen.
        for (int appWidgetID : appWidgetIds) {
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
                                                      R.layout.my_widget);

            remoteViews.setTextViewText(R.id.textView_output, message);
            remoteViews.setOnClickPendingIntent(R.id.button_update,
                                                getPendingSelfIntent(context,
                                                           ACTION_UPDATE_CLICK)
            );

            appWidgetManager.updateAppWidget(appWidgetID, remoteViews);

        }
    }

    /**
     * A general technique for calling the onUpdate method,
     * requiring only the context parameter.
     *
     * @author John Bentley, based on Android-er code.
     * @see <a href="http://android-er.blogspot.com
     * .au/2010/10/update-widget-in-onreceive-method.html">
     * Android-er > 2010-10-19 > Update Widget in onReceive() method</a>
     */
    private void onUpdate(Context context) {
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance
                (context);

        // Uses getClass().getName() rather than MyWidget.class.getName() for
        // portability into any App Widget Provider Class
        ComponentName thisAppWidgetComponentName =
                new ComponentName(context.getPackageName(),getClass().getName()
        );
        int[] appWidgetIds = appWidgetManager.getAppWidgetIds(
                thisAppWidgetComponentName);
        onUpdate(context, appWidgetManager, appWidgetIds);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);

        if (ACTION_UPDATE_CLICK.equals(intent.getAction())) {
            onUpdate(context);
        }
    }

}

The widget looks like this

Widget update button example. Simple counting.

This builds on the getPendingSelfIntent work of @Kels, @SharonHaimPour and @Erti-ChrisEelmaa.

It also builds on Android-er > 2010-10-19 > Update Widget in onReceive() method (not me) where it is demonstrated how to call onUpdate from onReceive, on an App Widget instance basis. I make that code general and wrap it in callOnUpdate.

John Bentley
  • 1,676
  • 1
  • 16
  • 18
11
protected PendingIntent getPendingSelfIntent(Context context, String action) {
    Intent intent = new Intent(context, getClass());
    intent.setAction(action);
    return PendingIntent.getBroadcast(context, 0, intent, 0);
}

views.setOnClickPendingIntent(R.id.Timm, getPendingSelfIntent(context,
                              "ham"));

Also prefer URL :

How to correctly handle click events on Widget

If you solved it in a different way, please provide this as an answer

Community
  • 1
  • 1
Kels
  • 674
  • 1
  • 5
  • 21
1

In the pendingIntent, we can also put extra attribute appWidgetId to reuse it later in onReceive to update the widget clicked widget instance

class ExampleAppWidgetProvider : AppWidgetProvider() {

    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray {

        appWidgetIds.forEach { appWidgetId ->
            Log.e("TAG", "onUpdate $appWidgetId")
            val pendingRefreshClickIntent: PendingIntent = Intent(context, javaClass).let {
                it.action = ACTION_REFRESH_CLICK
                it.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                return@let PendingIntent.getBroadcast(
                    context,
                    appWidgetId, // click in all instances widget will work well (base on Alireza Mirian comment in the top answer)
                    it,
                    PendingIntent.FLAG_UPDATE_CURRENT
                )
            }

            val views = RemoteViews(
                context.packageName,
                R.layout.example_appwidget
            )
            views.setOnClickPendingIntent(R.id.button_refresh, pendingRefreshClickIntent)

            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }

    override fun onReceive(context: Context?, intent: Intent?) {
        super.onReceive(context, intent)
        Log.i("TAG", "onReceive " + intent?.action)

        if (intent?.action == ACTION_REFRESH_CLICK) {
            val appWidgetId = intent.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID) ?: return
            Log.i("TAG", "onReceive appWidgetId $appWidgetId")

            val appWidgetManager = AppWidgetManager.getInstance(context)
            val views = RemoteViews(context!!.packageName, R.layout.example_appwidget)

            views.setTextViewText(R.id.text_data, "a " + (Math.random() * 9).roundToInt())
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }

    companion object {
        private const val ACTION_REFRESH_CLICK =  "com.example.androidwidgetbuttonclick.action.ACTION_REFRESH_CLICK"
    }
}

Widget initial layout

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/text_data"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="AA"
        android:textSize="20sp" />

    <Button
        android:id="@+id/button_refresh"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Refresh" />
</LinearLayout>
Linh
  • 57,942
  • 23
  • 262
  • 279
0

I tried the solution suggested by Sharon Haim Pour above, but my onReceive() method in AppWidgetProvider class has never been called on button press.

Intent intent = new Intent(WIDGET_BUTTON);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 
PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.MY_BUTTON_ID, pendingIntent );

After some research I could resolve the problem by updating the code as below:

Intent intent = new Intent(context, MY_APPWIDGETPROVIDER_CLASS.class);
intent.setAction(WIDGET_BUTTON);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 
PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.MY_BUTTON_ID, pendingIntent );

Do not forget to put below:

appWidgetManager.updateAppWidget(appWidgetId, views);
Taha
  • 117
  • 1
  • 11
0

Unlike the other answers here which use onReceive(), I found that it's actually a lot cleaner and simpler to do everything in onUpdate().

The official Android codelab Advanced Android 02.1: App widgets offers this solution. The example code there is in Java. Here I present the solution in Kotlin.

class MyAppWidgetProvider : AppWidgetProvider() {

    override fun onUpdate(
        context: Context?,
        appWidgetManager: AppWidgetManager?,
        appWidgetIds: IntArray?
    ) {
        appWidgetIds?.forEach { appWidgetId ->
            val views = RemoteViews(
                context?.packageName,
                R.layout.appwidget
            )
            // Coroutine to perform background IO task.
            GlobalScope.launch(Dispatchers.IO) {
                // Suspend function.
                val apiData = Api.retrofitService.getData()
                updateWidgetUI(views, apiData)
                context?.let {
                    views.setOnClickPendingIntent(
                        R.id.widget_button,
                        getUpdatePendingIntent(it, appWidgetId)
                    )
                }
                appWidgetManager?.updateAppWidget(appWidgetId, views)
            }
        }
    }

    private fun updateWidgetUI(views: RemoteViews, apiData: ApiData){
        views.apply {
            setTextViewText(R.id.widget_value_textview, apiData.value)
            setTextViewText(
                R.id.widget_last_updated_value_textview,
                DateFormat.getTimeInstance(DateFormat.MEDIUM).format(Date())
            )
        }
    }

    private fun getUpdatePendingIntent(context: Context, appWidgetId: Int): PendingIntent {
        val intent = Intent(context, MyAppWidgetProvider::class.java).also {
            it.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
            // It's very important to use intArrayOf instead of arrayOf,
            // as a primitive int array is expected.
            it.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(appWidgetId))
        }
        // Set the immutability flag for Android 12.
        val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        } else {
            PendingIntent.FLAG_UPDATE_CURRENT
        }
        return PendingIntent.getBroadcast(
            context,
            appWidgetId,
            intent,
            flags
        )
    }
// No need for onReceive().
}

The key here is to use the built-int AppWidgetManager.ACTION_APPWIDGET_UPDATE action instead of a custom action.