Android widget onclick listener for several buttons
Asked Answered
C

5

40

I am trying to create a widget for my application. From my reading an android developer site your onclick listeners all need to have an Intent. But what if I just want my button to update data in the widget itself and I don't want to start a new activity?

Here is some Android demo code:

Intent intent = new Intent(context, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);

// Get the layout for the App Widget and attach an on-click listener
// to the button
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
views.setOnClickPendingIntent(R.id.button, pendingIntent);

I want a button that when I click makes a http web call and then displays the results in the widget. How do I go about doing this if I have to use intents? Also I need to be able to differentiate between which buttons where clicked.

Why do widgets use intents and not the normal onclick listener where it calls a function like activities?

EDIT

My widget provider:

public class MyWidgetProvider extends AppWidgetProvider {

private static final String MyOnClick1 = "myOnClickTag1";
private static final String MyOnClick2 = "myOnClickTag2";
private static final String MyOnClick3 = "myOnClickTag3";

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

    // Get all ids
    ComponentName thisWidget = new ComponentName(context, MyWidgetProvider.class);
    int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);

    for (int widgetId : allWidgetIds) {

        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);

        remoteViews.setOnClickPendingIntent(R.id.widget_button_stayarm, getPendingSelfIntent(context, MyOnClick1));
        remoteViews.setOnClickPendingIntent(R.id.widget_button_awayarm, getPendingSelfIntent(context, MyOnClick2));
        remoteViews.setOnClickPendingIntent(R.id.widget_button_dissarm, getPendingSelfIntent(context, MyOnClick3));

        remoteViews.setTextViewText(R.id.widget_textview_gpscoords, "gps cords");

        appWidgetManager.updateAppWidget(widgetId, remoteViews);
    }
}

protected PendingIntent getPendingSelfIntent(Context context, String action) {
    Intent intent = new Intent(context, getClass());
    intent.setAction(action);
    return PendingIntent.getBroadcast(context, 0, intent, 0);
}

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

    if (MyOnClick1.equals(intent.getAction())) {
        // your onClick action is here
        Toast.makeText(context, "Button1", Toast.LENGTH_SHORT).show();
        Log.w("Widget", "Clicked button1");
    } else if (MyOnClick2.equals(intent.getAction())) {
        Toast.makeText(context, "Button2", Toast.LENGTH_SHORT).show();
        Log.w("Widget", "Clicked button2");
    } else if (MyOnClick3.equals(intent.getAction())) {
        Toast.makeText(context, "Button3", Toast.LENGTH_SHORT).show();
        Log.w("Widget", "Clicked button3");
    }
};
}

My Android manifest:

<receiver
    android:name="widget.MyWidgetProvider"
    android:icon="@drawable/fsk"
    android:label="FSK Widget" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>

    <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/example_appwidget_info" />
</receiver>
Cureall answered 22/4, 2014 at 13:15 Comment(0)
F
61

It is possible to make an onClick event for Views in Widgets. You can create as many onClick events as you want.

On top of your Widget class, create a static variable, which will be your onClick name tag:

private static final String MyOnClick = "myOnClickTag";

Define a helper method to automate the creation of each PendingIntent:

protected PendingIntent getPendingSelfIntent(Context context, String action) {
    Intent intent = new Intent(context, getClass());
    intent.setAction(action);
    return PendingIntent.getBroadcast(context, 0, intent, 0);
}

Set this onClick tag to your view as below:

    remoteViews.setOnClickPendingIntent(R.id.button, 
                      getPendingSelfIntent(context, MyOnClick));

create an onReceive method in your Widget class and set this onClick event inside it:

public void onReceive(Context context, Intent intent) {

    if (MyOnClick.equals(intent.getAction())){
        //your onClick action is here
    }
};

Whenever the view that you set the tag is pressed, onReceive will capture that and will do the action just the same as our everyday, standard onClick event.

Edit: According to your answer, can you replace your onUpdate content with following lines and try again:

    RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.widget_det);
    thisWidget = new ComponentName(context, MyWidgetProvider.class);    
    remoteViews.setOnClickPendingIntent(R.id.widget_button_stayarm, getPendingSelfIntent(context, MyOnClick1));
    remoteViews.setOnClickPendingIntent(R.id.widget_button_awayarm, getPendingSelfIntent(context, MyOnClick2));
    remoteViews.setOnClickPendingIntent(R.id.widget_button_dissarm, getPendingSelfIntent(context, MyOnClick3));
    remoteViews.setTextViewText(R.id.widget_textview_gpscoords, "gps cords");
    appWidgetManager.updateAppWidget(thisWidget, remoteViews);
Fionnula answered 22/4, 2014 at 13:26 Comment(10)
Ok so you use the action tag to differentiate between you buttons clicked.Cureall
Is this using a Widget Service or a widget Provider?Cureall
Yes exactly you ll use action tag to differentiate buttons clicks. You can set the same one for 2 different buttons, or you can create many onclick events and set them seperately. No it is not a service. Classical Widget class which extends AppWidgetProviderFionnula
getPendingSelfIntent(context, MyOnClick)is underlined red for me?Cureall
sorry it is my bad :) adding to my answerFionnula
Sorry to be such a pain, But have implemented it now, But when I click on a button in the widget it doesn't call the onrecive function? I have set a break point and it doesnt get hit? I have pasted my code in the original answer.Cureall
no worries hope we will solve this problem. widgets were a big problem for me as well until I've managed to run them flawlessly. Can you change your whole onUpdate content with the code I wrote in my answer please? and put a log inside there as well to checkFionnula
+1 If only I had found this answer earlier - it would have saved me so much time! Easy to follow and clearly explained - thank you!Lauderdale
@Fionnula i set setOnClickPendingIntent to my views but view click time onRecive not fire?Outwash
Warning: the answer by Canova doesn't work on all scenarios. Read this: ( developer.android.com/topic/performance/appstandby?hl=es-419 ). I figured out that the Android OS decides when to call the broadcast events depending on the category (bucket) the app is currently categorized (this is decided by the user's usage of the app). Any broadcast event pending to sent can take X amount of time depending on this, so it will NOT happen immediately (causing bugs in code), and if the app is manually terminated by the user, the broadcast event will NEVER be received (the OS blocks them).Long
L
21

just call the super in your onReceive method

@Override
public void onReceive(Context context, Intent intent) {
    super.onReceive(context, intent);//add this line
    if (MyOnClick1.equals(intent.getAction())) {
        // your onClick action is here
        Toast.makeText(context, "Button1", Toast.LENGTH_SHORT).show();
        Log.w("Widget", "Clicked button1");
    } else if (MyOnClick2.equals(intent.getAction())) {
        Toast.makeText(context, "Button2", Toast.LENGTH_SHORT).show();
        Log.w("Widget", "Clicked button2");
    } else if (MyOnClick3.equals(intent.getAction())) {
        Toast.makeText(context, "Button3", Toast.LENGTH_SHORT).show();
        Log.w("Widget", "Clicked button3");
    }
}
Later answered 10/12, 2014 at 10:0 Comment(1)
The super.onReceive(context, intent); solved my problem. Thank you!Lauderdale
C
3

here is the example:

public class SimpleWidgetProvider extends AppWidgetProvider {

    private static final String MY_BUTTTON_START = "myButtonStart";
    RemoteViews remoteViews;

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        final int count = appWidgetIds.length;

        for (int i = 0; i < count; i++) {
            int widgetId = appWidgetIds[i];

            remoteViews = new RemoteViews(context.getPackageName(),
                R.layout.widget_layout);
            remoteViews.setTextViewText(R.id.textView, number);

            Intent intent = new Intent(context, SimpleWidgetProvider.class);
            intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
            PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
                0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

            remoteViews.setOnClickPendingIntent(R.id.actionButton,
                getPendingSelfIntent(context, MY_BUTTTON_START));

            appWidgetManager.updateAppWidget(widgetId, remoteViews);
        }
    }

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

        if (MY_BUTTTON_START.equals(intent.getAction())){
            Toast.makeText(context, "Click", Toast.LENGTH_SHORT).show();
        }
    };

    protected PendingIntent getPendingSelfIntent(Context context, String action) {
        Intent intent = new Intent(context, getClass());
        intent.setAction(action);
        return PendingIntent.getBroadcast(context, 0, intent, 0);
    }
}

Centaurus answered 4/8, 2017 at 17:24 Comment(2)
Can you help me. I'm using the same code, but onReceive is never called.Boudreau
Thanks a lot Faxriddin for your quick response, I just got it fixed. I was missing (context,getClass()) in the following line: Intent intent = new Intent(context, getClass());Boudreau
L
0

For anyone trying to communicate the App Widget (or Home Screen Widget) through onClicks to the Flutter code keep in mind that the accepted answer doesn't work on all scenarios because the Broadcasts are managed by the OS and may be handled with delay (please read my comment on Canova answer for more info).

The solution I use is this one (which opens the app immediately and handles the navigation depending if the app was opened clicking the App Widget or not):

  1. Save the data you need inside the PendingIntent to be recovered later as stated here: Passing values in Pending Intents Android and then set the pending intent to a view like this (notice that my pending intent is a getActivity, not a getBroadcast, meaning the activity will be opened immediately):

    val intent = Intent(context, MainActivity::class.java)
                     intent.action = "es.antonborri.home_widget.action.LAUNCH"
    var flags = PendingIntent.FLAG_UPDATE_CURRENT
    if (Build.VERSION.SDK_INT >= 23) {
     flags = flags or PendingIntent.FLAG_IMMUTABLE
    }
    intent.putExtra( "keyHomeScreenWidgetOpen", true)
    val openAppIntent = PendingIntent.getActivity(context, 0, intent, flags)
    setOnClickPendingIntent(R.id.homeScreenWidgetImage, openAppIntent)
    
  2. Create a Method Channel handler on your MainActivity.kt as stated here: https://docs.flutter.dev/platform-integration/platform-channels?tab=type-mappings-kotlin-tab

  3. Recover the value we saved on step 1 and save it on the preferences:

    private fun getInitialIntent() : Boolean {
     try {
         val extras: Bundle? = intent.extras
         if (extras != null) {
             val widgetData: SharedPreferences =
                 applicationContext.getSharedPreferences("HomeWidgetPreferences", Context.MODE_PRIVATE)
             val openedByHomeScreenWidget = extras.getBoolean("keyHomeScreenWidgetOpen")
             if (openedByHomeScreenWidget) {
                 var preferences = widgetData.edit()
                 preferences.putBoolean("keyHomeScreenWidgetOpen", true)
                 preferences.commit()
                 return true
             }
         }
         return false
     }
     catch (error:Exception){
         return false
     }
    

    }

  4. Call the method through the method channel on your main.dart to receive a confirmation of the operation:

     MethodChannel channel =
     const MethodChannel(Constants.CHANNEL_HOME_SCREEN_WIDGET);
     bool openedByHomeScreenWidget =
     await channel.invokeMethod("getInitialIntent");
     if (kDebugMode) {
      print(
       "openedByWidget: $openedByHomeScreenWidget");
     }
    
  5. Now you can access the data wherever you want through the preferences. Remember the value we saved on step 1 was saved on the preferences on step 3. The step 4 is only to have a feedback from the method channel on the dart side of the code.

PD: I'm using the HomeWidget plugin on my flutter project...I recommend u implement it too to create your App Widget.

Hope this helps someone else...happy coding y'all!

Long answered 18/6, 2023 at 4:48 Comment(0)
D
0

Don't forget to write your action tag in the manifest file, otherwise it won't work.

Disgust answered 8/12, 2023 at 9:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.