0

I currently have the most basic widget possible for attempting to force an update on a button press, a single button which updates its own label from "old text" to "new text".

This has worked exactly once, and then never again. According to the logs it should be working. All I want, is a consistent way to update my widget on a button press.

This is the entirety of the code aside from an empty main activity and XML files.

public class ExampleAppWidgetProvider extends AppWidgetProvider {
    private static final String MyOnClick = "myOnClickTag";
    private static final String REFRESH_ACTION = "refresh";
    String globalString = "Blank";
    static boolean globalBoolean = false;

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

            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.example_widget);
            views.setOnClickPendingIntent(R.id.example_widget_button, getPendingSelfIntent(context, MyOnClick, appWidgetIds));

            if(globalBoolean == true) {
                views.setCharSequence(R.id.example_widget_button, "setText", "new text");
                Log.w("Widget", "displaying new text");
            }else{
                views.setCharSequence(R.id.example_widget_button, "setText", "old text");
                Log.w("Widget", "displaying old text");
            }

            appWidgetManager.updateAppWidget(appWidgetId, views);
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds);
    }

    protected PendingIntent getPendingSelfIntent(Context context, String action, int[] appWidgetIds) {
        Intent intent = new Intent(context, getClass());
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
        intent.setAction(action);
        return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_MUTABLE);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);//add this line
        if (MyOnClick.equals(intent.getAction())) {
            globalString = "new text";
            globalBoolean = !globalBoolean;

            Bundle extras = intent.getExtras();
            if(extras != null) {
                int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
                if (appWidgetIds != null && appWidgetIds.length > 0) {
                    Log.w("Widget", "Calling onUpdate()");
                    this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
                }
            }
        }
    }
}

When I press the button I can see from the logs that it does enter onUpdate() and it even goes into the correct part of the if statement:

2023-01-18 21:08:16.900 17447-17447/com.example.widget_test_2 W/Widget: Calling onUpdate() 2023-01-18 21:08:16.901 17447-17447/com.example.widget_test_2 W/Widget: displaying new text

And yet, the widget does not graphically update.

I think that what I saw originally might have been a fluke. While learning to make widgets I followed Coding In Flows widget tutorial which is now 5 years old, and it only updates graphically once per hour despite also forcing an update on click (a lot of that tutorial doesnt work anymore). I think in 2023 I must need to do something else in order to force this update to work consistently, but the majority of the posts I can find on this subject are pretty old, or I've attempted, or don't answer my question.

Can someone please provide a code example of what I need to change to force an update?

Thanks!

  • You can't keep state in instance or `static` fields because a new `AppWidgetProvider` instance – and possibly a new process – is created for each Widget action; i.e., `UPDATE`, `ENABLED`, `DELETED`, etc. You'll need to persist that to, for example, a database or file. Also, the user can create multiple instances of the Widget itself, so you need to account for possibly multiple active IDs at once. Here's an old minimal example that I modified slightly for this setup: https://drive.google.com/file/d/1EFo7prmoOdLohe9vukaUAC6mSJTbEWC0/view?usp=share_link. It saves to `SharedPreferences`. – Mike M. Jan 19 '23 at 20:45
  • Thanks. I had to make some slight changes. BuildConfig.APPLICATION_ID doesnt seem to work for me so I just pasted in the applicationId string from the gradle. Also the widget info.xml needed a lot more in it. But now I have a working template! I would be lying if I said I understood it all. I cannot seem to figure out how to differentiate clicks on different buttons. It looks like appWidgetId is used as the key for all clicks, how would you suggest I modify the code for multiple buttons. I think I want to keep the appWidgetId but also add another field to the shared preferences? – Luke Mills Jan 21 '23 at 20:07
  • `BuildConfig` often won't resolve unless you have a current valid build; you can compile while it shows an error. Didn't have to be exactly that anyway; just needs to be a relatively unique string. Also, that was a _minimal_ example to demo clicks, nothing else. The `widget_info` file technically isn't required at all, and that example works exactly as expected. It was tested on multiple devices before posting. Anyway, yeah, if you need to track more buttons per Widget, you'll need to add some other identifier to your saved state, but `SharedPreferences` is not going to work well if things… – Mike M. Jan 21 '23 at 21:11
  • …get much more complex. You should look into using a database. If you really want to stick with preferences, you'll need to devise some way translate all that saved stuff into key-value pairs, 'cause that's all it handles. For example, you could use keys like `widget_button_one_clicked:ID`, `widget_button_two_clicked:ID`, etc., and have a separate entry for each button in each Widget. Alternatively, you could use one entry per Widget – e.g, with keys like `widget:ID` – and encode all of its buttons' state into one string – e.g., `"on;off;off;on"` – but that takes more processing, obviously. – Mike M. Jan 21 '23 at 21:11
  • I probably implemented the info.xml wrong because nothing was showing up in my widgets menu (I am not a java pro). I am doing something sort of similar (i think) to what you suggest, I have an arraylist saved to a gson which i am able to load, modify, and save from in my widget. All I need is a maximum of 8 image buttons which change their image based on their status. I really do not understand this keying though, how can I merge a key and the appWidgetId to work with the example code? Or what search terms can I use to find out more about this? Nothing I've tried wants to work. Thanks! – Luke Mills Jan 22 '23 at 14:26
  • Yeah, it can take a minute to get your head around; I'll try to clarify. The ultimate goal is to keep distinct settings for each instance (ID) of your Widget. For my simple example, it's sufficient to use `SharedPreferences`, which basically just manages an XML file containing values each labeled with a key to identify it, but those keys have to be unique within the file. That's all I'm doing with the key+ID stuff in both of the alternatives mentioned above: making sure to keep distinct settings within a single `SharedPreferences` file. If this were a database, it'd be cake 'cause we can look… – Mike M. Jan 23 '23 at 02:30
  • …up a whole row of individual values with just an ID, but with `SharedPreferences` we only get one value per key. There is a third option which I neglected to mention which might've been easier to grasp: a separate file for each Widget ID, where you wouldn't have to worry about creating unique keys at runtime. In any case, you're also going to have to send more information on the `Intent` to tell which button was clicked. Here's an example with separate files: https://drive.google.com/file/d/1EJcGA8nVPP6VEEuzaTYZWz-ZJQUPnEb3/view?usp=share_link. Looks like: https://i.stack.imgur.com/AQpIj.gif. – Mike M. Jan 23 '23 at 02:30
  • If you'd rather use a single file with one key per Widget instance, which sounds like what you're currently trying, I can modify that example, but I won't use Gson, as it's huge overkill. – Mike M. Jan 23 '23 at 02:30
  • Thanks! What you provided is working great for me so far, I was able to get the basic functionality I was looking for to work. It is easier to understand now that I can see it working. I don't fully understand it, I need to read up on URI for example, but I think as I work with it more it will start to make more sense. Do you want to submit your code(s) as an answer? and I can mark this as answered. I think it would be pretty helpful for newer app developers as there aren't a lot of tutorials for something this complex. – Luke Mills Jan 25 '23 at 20:24
  • No problem. The URI thing is kinda cheating, and mildly hacky. We need a distinct `PendingIntent` for each button, but the system will only give us a new one if it's different than all the active ones we have already. Unfortunately, the `Intent`'s extras are not considered when comparing to the old ones, so we have to differentiate them with other properties. We pass the `appWidgetId` in the `getBroadcast()` call as the `requestCode`, which is used in the comparisons. That only makes it distinct down to the Widget instance, though, so I used the data URI to make them distinct for the buttons. – Mike M. Jan 26 '23 at 03:17
  • We could've used a different action or category for each, but I thought that would be too confusing. There's a newer property called `identifier` that would've been much more appropriate, but it's not available until API level 29. We only care about the third part of the URI, btw, the fragment; the other two parts don't matter at all, and can be anything, really. Anyhoo, glad I could help, but I'm not posting new answers here atm. Please feel free to finish this up however you like. Thanks, though. I appreciate the offer. Cheers! – Mike M. Jan 26 '23 at 03:17

0 Answers0