0

So I'm seeing some bizarre behavior in an appwidget that I wrote.

The widget itself is quite simple - it reads a few values from persistent storage and then displays them as text. Nothing fancy in the layout - just a FrameLayout root element with some Linear Layout and TextView children.

The widget has a simple configuration activity associated with it.

The bizarre behavior is that the widget will initially show "Problem Loading Widget" after the user closes the configuration activity, and then after a few seconds it shows a "Google Sound Search" button (and clicking on the button actually does launch Google Sound Search). Then, after a few more seconds, it finally shows the expected display.

I am away from my code right now, so I'll have to wait until tonight to post code snippets. However, in the meantime, can anyone provide some insight into how such a thing could happen? Has anyone else ever experienced this? How could another widget "hijack" mine?

Thanks, -Ron

Here are some screenshots: Step 1 - Problem Loading Widget Step 2 - Google Sound Search hijacks widget Step 3 - Success!  Widget displays properly

rothloup
  • 998
  • 8
  • 21

2 Answers2

1

There are a couple of issues with your widget and there are answers to all of them (although you didn't post any code so some of my statements are based on assumptions):

"Problem loading widget": this is the default view Android uses before the widget is initialized and the layout updated. Simply add the following line to your widget xml configuration (to show a loading message instead of the problem message):

android:initialLayout="@layout/my_cool_widget_loading_message"

If the widget shows the wrong layout then you probably have an issue in the widget's onReceive method. onReceive is called for all the widgets no matter whether the broadcast is for that specific widget. Android's AppWidgetProvider filters the broadcasts by appwidget Id and dispatches to the other methods (like onUpdate).

See also: https://developer.android.com/reference/android/appwidget/AppWidgetProvider.html#onReceive(android.content.Context, android.content.Intent).

If you override onReceive (which I assume you do), you need to call through to super.onReceive(Context, Intent) to make sure your other methods don't get calls meant for other widgets.

Now for the configuration of the widget. If you follow the Google documentation then it will all work nicely. The only improvement I'd do is what my other answer that you reference suggests (https://stackoverflow.com/a/14991479/534471). This will NOT send out two broadcasts. The setResult()/finish() part does only terminate the config Activity and let Android know whether to actually add the widget or not (depending on whether the result is RESULT_CANCELED or RESULT_OK.

From your own answer I can see why your code wouldn't work. The code is:

Intent intent = new Intent();
    intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {mAppWidgetId});
    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
    setResult(RESULT_OK, intent);
    sendBroadcast(intent);
    finish();

First of all there's no need to add the appWidgetId twice, use the AppWidgetManager.EXTRA_APPWIDGET_IDS version and you're good. Second you're using the same Intent to return as a result for the Activity. AFAIK it's not documented what happens when you do set an action on that Intent but my experience with Android widgets is that you need to stick exactly to the documentation or you'll end up having strange issues (like the ones you encounter). So please use two different Intents. Activity result:

Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();

Broadcast:

Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null, this, MyWidget.class);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {mAppWidgetId});
        sendBroadcast(intent);
Community
  • 1
  • 1
Emanuel Moecklin
  • 25,943
  • 11
  • 66
  • 78
  • Thanks for your post, I learned a few things from it. I realize I was mistaken that setResult() sends a broadcast (will edit my answer), and now I know where the "Problem Loading Widget" comes from. However, I still maintain that the behavior departs from the docs a little bit. Docs say config activity must update the view - but my observation is that simply using setResult() (docs state as a separate requirement) is sufficient to get the OS to draw the widget after config is done. If I add your suggested broadcast, it has no effect. Perhaps this is difference in API versions? Using 23. – rothloup Oct 08 '16 at 19:18
  • 1
    This is only true if the widget is newly created but not if the Activity is used to configure the widget after it has already been added to the home screen (some of my widgets have a settings button that opens the same configuration Activity). This has nothing to do with the API version, I have been testing my widgets on about 30 different real devices with a range of Android versions from 2.2 up to 7.0 (no 7.1 yet ;-). – Emanuel Moecklin Oct 10 '16 at 12:34
  • ah, I see. My widgets are only configured when they are placed. I'll keep your statement in mind when it comes time to reconfigure an existing widget. thank you! – rothloup Oct 10 '16 at 13:24
0

ok, so I figured it out. Posting here in case anyone else runs into this. I think that the Android Developer docs are a little misleading here.

The problem was that in my configuration Activity, I had this code at the end:

    Intent intent = new Intent();
    intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {mAppWidgetId});
    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
    setResult(RESULT_OK, intent);
    sendBroadcast(intent);
    finish();

Providing an intent with the extra EXTRA_APPWIDGET_ID is recommended by the documentation provided by google.

However, that same document says that you have to update the widget's view by creating a RemoteView and calling AppWidgetManager.updateAppWidget() like so:

RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.example_appwidget);
appWidgetManager.updateAppWidget(mAppWidgetId, views);

I didn't like the idea of placing the presentation logic in both the configuration activity and the widget class, so I instead decided to broadcast an intent at the end of the configuration activity to tell the widget to redraw itself. That's why I have setResult() AND sendBroadcast() at the end of the activity. The documentation further states that the onUpdate() callback will not be called when using a configuration activity. So this seemed neccessary. I added the ACTION_APPWIDGET_UPDATE and the EXTRA_APPWIDGET_IDS to the intent so that it would trigger the onUpdate() method. This practice was recommended by this SO answer (albeit without being included in the activity result intent - but I tried separating the two and it had no effect).

Now I'm not certain exactly how the "Google Sound Search" widget got in there, nor do I fully understand the mechanics of how the intents interacted to produce the observed results. However, as soon as I replaced my code above with the code stated in the docs, the widget was updated properly.

    Intent resultIntent = new Intent();
    resultIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
    setResult(RESULT_OK, resultIntent);
    finish();

This seems to contradict the documentation's statement that the configuration activity must update the widget's view. Simply providing the configuration activity result as below triggers the onUpdate() method in the widget, thus allowing the widget to redraw itself. I confirmed the behavior on an emulator running API 23 and also on a Samsung device running Samsung's android flavor.

Community
  • 1
  • 1
rothloup
  • 998
  • 8
  • 21