Android - how can I handle restarts of appWidgets containers? - android

I'm creating both an appWidget container and appWidgets.
I have a problem which I think is because of the created appWidgets - each time I restart (kill and then open) the appWidget container, the appWidget is shown, but it no longer handles clicks.
This occurs on every appWidget container (launchers and even widgetLocker), so that's why I suspect it's because of the appWidget.
I've tried many tutorials online, but I couldn't find any reference about this problem.
Maybe the preparation of the intents (OK, pendingIntents) of the buttons should not be created on the onUpdate method alone? If so, where should I add it? I've added it on the onReceive, but it takes a very long time till that occurs (if at all).
I would have added some code here, but any code online gave me the same results.
How can I fix it?

OK, I am not sure what caused the problem, but here is the solution.
In short:
onUpdate(...)
{
super.onUpdate(context, appWidgetManager, appWidgetIds);
for (int appWidgetId: appWidgetIds)
{
// Prepare remoteViews, including registration of their clicks events...
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
}
}
onReceive(...)
{
if (intent.getAction().equals(ACTION_BUTTON_CLICKED))
{
// Get needed data from the customized intent.
// Prepare remoteViews of what should be updated, including
// registration of their clicks events...
appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
}
}

Related

Android AppWidgetManager method updateAppWidget fails to set intents, load data. And it happens randomly

My widget is comprised of 2 buttons and a listview displaying data. Most times, when the widget provider's onUpdate method is called, everything loads normally and everyone is happy.
However I've noticed sometimes after the update method is called, the widget just completely fails to load its data. The listview is empty, and all of the buttons are non-responsive. It's as if I initialized the layout into the widget, but none of the pending intents nor the adapter for list were set.
I logged everything and found that this isn't the case. The pending intents ARE created, as is the list adapter, every time, including the random time when it fails. For a long time I thought it had to do with how the list data is populated into the adapter, but seeing it work every single time, coupled with the fact that the button intents don't work as well leads me to believe the method updateAppWidget(ComponentName, RemoteViews) is what is failing. However, there are no error stacks to help me confirm this.
Here is the code which runs in a separate service called by the AppWidgetProvider's onUpdate method:
#SuppressWarnings("deprecation")
private void updateWidget(int[] ids) {
AppWidgetManager widgetManager = AppWidgetManager.getInstance(this);
int[] widgetIds = widgetManager.getAppWidgetIds(new ComponentName(this, WidgetReceiver.class));
for (int currentWidgetId : widgetIds) {
RemoteViews widget = new RemoteViews(this.getPackageName(), R.layout.widget_layout);
Intent intent = new Intent(this, WidgetService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, currentWidgetId);
intent.putExtra("random", randomNumber);
randomNumber++;
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
widget.setRemoteAdapter(currentWidgetId, android.R.id.list, intent);
Intent clickIntent = new Intent(this, DetailsActivity.class);
PendingIntent pending = PendingIntent.getActivity(this, 0, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
widget.setPendingIntentTemplate(android.R.id.list, pending);
Intent settingsIntent = new Intent(this, WidgetSettingsActivity.class);
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent settingsPending = PendingIntent.getActivity(this, 0, settingsIntent, PendingIntent.FLAG_UPDATE_CURRENT);
widget.setOnClickPendingIntent(R.id.widget_settings, settingsPending);
Intent mainIntent = new Intent(this, MainActivity.class);
PendingIntent mainPending = PendingIntent.getActivity(this, 0, mainIntent, PendingIntent.FLAG_UPDATE_CURRENT);
widget.setOnClickPendingIntent(R.id.widget_logo, mainPending);
ComponentName widgetCompName = new ComponentName(this, WidgetReceiver.class);
widgetManager.updateAppWidget(widgetCompName, widget);
}
}
What is most frustrating about this bug is that I can't reliably recreate it. Sometimes I get (un)lucky and it shows it ugly head, other times (the majority of the time) it works perfectly.
Another thing that I thought was interesting is that I've seen the same exact problem in Facebook's widget. Due to NDA I can't show a screen of my app when it fails, but I can show a screen of Facebook's identical problem:
When Facebook's widget looks like this, it has the exact same issues as mine. No data loaded, all buttons are unresponsive.
For both Facebook and my widget, a subsequent update interval will usually make the problem go away.
As much as I appreciate that I'm not the only one who is running into this, I still don't want to release until I fix this. Has anyone here run into this, and better yet found a solution, or even a cause of the problem? Thanks for helping.
EDIT: Something interesting. I ran an experiment where I set the update interval to a full day rather than every 30 minutes. My hypothesis was that maybe the update method wasn't the cause, it was something else which was causing the widget to become blank and unresponsive.
Sure enough, after about 2 hours, I checked my phone and the widget was dead, despite no update method being called. This would lead me to believe that something else is causing this problem, NOT widgetManager.updateAppWidget(widgetCompName, widget); like I previously thought.
I know that a configuration change can cause the widget to be rebuilt, and thus it is possible that it can fail. However, I already use the Service class's onConfigurationChanged method to reload the widget if necessary. Is there another case like configuration change which can cause the widget to destroy and recreate itself?
After a lot of blood, sweat, and tears I found the solution. I am going to hold off on confirming this answer for a few days to make sure that the error doesn't pop back up, but after a lot of observation I think it is resolved.
So I was right in my edited comments in the original question. It wasn't the update method of the AppWidgetManager that caused the problem, but rather some other Android process which caused the app widget to recreate itself. Unfortunately I couldn't isolate that trigger, but I did find a solution.
In my RemoteViewsFactory class (basically the wrapper which is used to load the data set), I had a block of code that looked like this:
RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.widget_layout);
if(mItems.size() == 0)
views.setViewVisibility(R.id.widget_empty, View.VISIBLE);
else
views.setViewVisibility(R.id.widget_empty, View.GONE);
AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
ComponentName widgetCompName = new ComponentName(mContext, WidgetReceiver.class);
manager.updateAppWidget(widgetCompName, views);
Basically, if the list was empty, I showed a Loading message that took up the entire area where the listview is located. If the list wasn't empty, I'd just hide that message.
So this is what was happening: When the factory was destroyed (presumably for memory purposes) the widget itself was not. So all of the code which sets the intents for the data and stuff was not run. However, the factory was recreated, which ran that block of code which updated the app widget with the new remote views object. This remote views object didn't do any of the methods that you see in my onUpdate() method I originally posted, so all of its features didn't work.
Moral of the story: DON'T use updateAppWidget in your RemoteViewsFactory class! Now it may work if you run all the necessary lines, but I can't confirm that.

First added widget not clickable. 2nd is

in my Broadcastreceiver, which is a widget at the same time. At the time I add the widget for the first time after installation, it is not clickable. The 2nd widget I add is clickable. While the first widget is still not clickable.
This errors happened while testing different versions in the emulator. I have no other device at hand, which I can reset all the time. For testing I always start with "Wipe user data" to be sure it is a fresh installation.
It does work for 1.6, but not for 2.x
My preference screen is a manual independent activity, which has to be started separably.
If you need more information, please tell me.
Here my code for making it clickable:
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
Log.d(LOG_TAG, "Update");
for (int id : appWidgetIds) {
Intent intent = new Intent(ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
//make widget clickable
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.main);
remoteViews.setOnClickPendingIntent(R.id.imageview, pendingIntent);
appWidgetManager.updateAppWidget(id, remoteViews);
}
}
Here is a long shot - change the request code in your PendingIntent.getBroadcast between the PendingIntent instances; I think that I had the same issue with one of my widgets and this change has solved it.
I found the error.
It is a bug in the android emulator alias AVD . Damn thing. Hope this helps others not wasting so much time in an non existing error.
http://code.google.com/p/android/issues/detail?id=8889
bug id 8889 "appWidgetManager.updateAppWidget not updating widgets on fresh AVDs under 2.0, 2.1"
A workaround is after starting with Wipe user data(I disabled save to snapshot for this - tested not with enabled). Than exit. Than start again without wipe.
Other wrote you can use ctrl+F11 two times to change to landscape mode and back.

Update a widget from a service

I'm trying to update a widget from a service... the service start a thread that execute this code
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(Worker.this);
RemoteViews remoteViews = new RemoteViews(Worker.this.getPackageName(), R.layout.mywidget);
ComponentName projectWidget = new ComponentName(Worker.this, MyWidget.class);
remoteViews.setTextViewText(R.id.widgetstate, "update");
appWidgetManager.updateAppWidget(projectWidget, remoteViews);
the onUpdate method just print a log, but this log is printed only when I create the widget, not everytime the thread cycles... where I'm wrong?
Thanks in advance
You're not wrong. You just update the widget without calling onUpdate(). It's just a convinience method that gets called in response to the ACTION_APPWIDGET_UPDATE broadcast by the system. You don't have to call it to update a widget, it also just works like you did here. ¹
To invoke it you can send the broadcast or call it by hand though, it's a good idea to keep the widget update functionality in one place from a code structure point of view to find things easily.
¹ Take a look at the AppWidgetProvider source, line 56. The whole AppWidgetProvider is just a BroadcastReceiver that does some work for you already - and it does start the update in the same way.

AppWidget click lost after system restarts my process

i am making an appwidget and i have problems with click event, which is lost when system kills the widget's process and later restarts it. this also happens after screen rotate.
building against SDK version 7 and running on emulator (2.1) and a real device with 2.3.3.
my onUpdate method:
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int wid : appWidgetIds) {
Log.i(TAG, "onUpdate widget #" + wid);
Intent intent = new Intent(context, MyClass.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, wid);
PendingIntent clickIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
RemoteViews widget = new RemoteViews(context.getPackageName(), R.layout.widget);
widget.setOnClickPendingIntent(R.id.widget_layout, clickIntent);
appWidgetManager.updateAppWidget(wid, widget);
}
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
where R.id.widget_layout is id of linear layout of the appwidget. i tried to add this click event also to a textview, but with same result.
i am fighting this problem for several days and i found some people with this same problem, but no solution works for me :( i also tried different pending intent flags without any success.
second problem is, when i add another appwidget on home screen, it does not react to click events. in logcat i see the message from onUpdate method "onUpdate widget #xy", but the appwidget does not react to clicks. only the first appwidget placed on home screen reacts to clicks, but only for some time. any ideas?
When you say the first widget stops responding to clicks, do you mean that the onUpdate method is not being called? Perhaps put some code in onEnabled(Context context) to see if that's being called instead, and if so, put whatever logic is necessary in that function. Also, you can catch intents via the onReceive method (found at the same link) to see which ones your widget is actually receiving.
Also, ensure that the Context that you have the receiver running in (the one that gets passed to this function) is an Application or Service, and not an Activity, or the reference to it may not persist.
You must also make sure that every time you update the widget with a RemoteViews object, you send all of the data needed to fully reconstruct the widget. This is because on, e.g., screen rotation, the system doesn't have anything to reconstruct the widget with beside the very latest RemoteViews you passed it.

After orientation change buttons on a widget are not responding [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
After orientation change buttons on a widget are not responding
I'm facing a problem with an appwidget that has one ImageView inside the xml layout for which I register a pendingintent that is handled in OnReceive method .
Everything works okay until I change phone orientation. At this point the widget doesn't work anymore, I click on the image but nothings happens.
This problem is exactly as the one here :
After orientation change buttons on a widget are not responding
What's is the problem and how can be resolved ?
Thanks.
I eventually managed to recreate the OP's service-free solution. Here's the secret for the record: Any time you update the remote views, you must update everything that you ever update. My app was updating some visual elements, but not setting the button handler again. This caused the handler to stop working--not straight away--only after a rotation change, hence the confusion.
If this is done right, you don't need to intercept configuration change broadcasts, the last remote views you set will be used again after rotation. No call is needed to your AppWidgetProvider.
I had a similar issue, but my app is working well now with the solution suggested by Alex
"...solved without the help of a service, just setting again the
setOnClickPendingIntent in the OnReceive method"
I tracked the onReceive method and it is called everytime I change the orientation of my device, so I called again the configuration of my widget on the onReceive method.
Anyway Im not sure this is the best solution to solve that problem, if anyone knows a better solution please share.
Thanks.
Whenever you update the look of your widget (using either an Activity or your Broadcast Receiver [App widget provider]), you must also reassign all the PendingIntents for the click handlers, and then call updateAppWidget() as normal.
Example with setTextViewText():
// This will update the Widget, but cause it to
// stop working after an orientation change.
updateWidget()
{
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
remoteViews.setTextViewText(R.id.widget_text_view, "Updated widget");
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
}
// This is the correct way to update the Widget,
// so that it works after orientation change.
updateWidget()
{
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
remoteViews.setTextViewText(R.id.widget_text_view, "Updated widget");
Intent intent = new Intent(context, MyWidgetActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, ...);
remoteViews.setOnClickPendingIntent(R.id.widget_click_button, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
}
See here for a possible explanation, or further things to try:
http://permalink.gmane.org/gmane.comp.handhelds.android.devel/98008
or
http://groups.google.com/group/android-developers/browse_thread/thread/578a4429c369c27c/273a53a96ddd10c5?lnk=gst&q=Widget+does+not+respond+when+phone+orientation+changes#273a53a96ddd10c5
But it's not yet clear what the true solution is.

Categories

Resources