Question

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>
Was it helpful?

Solution

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);

OTHER TIPS

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");
    }
}

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);
    }
}

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!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top