I've got a widget which will update itself whenever there's a configuration change (such as screen orientation), and whenever the phone is unlocked. This process involves setting onClick handlers for the buttons on my widget. This works well, however I have found that there's a usage case which causes my app to not respond to onClick events. This particular case is whenever the launcher restarts itself.
Is there a way to detect when a launcher restarts, so I can update my widget manually? Or is there another way to ensure onClick handlers are not lost?
Turns out I was spamming new RemoteViews() when I should have just called it once to produce the view, and then referred to that one instance when required. In my solution, I have a class variable which stores this single RemoteView instance, and a getter to access it.
Proposal by #Glitch might not work for certain cases, especially app widget with ListView. This is because ListView will get very slow (Try to scroll the ListView) after appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, list_id) had been called several time.
My guess is, the single RemoteView instance will keep all its executed instruction in a list. Over the time, the instruction list will grow. Every time appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, list_id), the large instruction list will be executed all over again.
My proposed solution is as follow. However, I believe it will only work on certain device, as not all devices will receive same broadcast message during launcher restarting.
#SuppressLint("NewApi")
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals("com.sec.android.widgetapp.APPWIDGET_RESIZE")) {
// http://stackoverflow.com/questions/17396045/how-to-catch-widget-size-changes-on-devices-where-onappwidgetoptionschanged-not
handleTouchWiz(context, intent);
// Possible launcher restart.
handleLauncherRestart(context, intent);
} else if (action.equals("android.appwidget.action.APPWIDGET_UPDATE_OPTIONS")) {
// Possible launcher restart.
handleLauncherRestart(context, intent);
}
super.onReceive(context, intent);
}
private void handleLauncherRestart(Context context, Intent intent) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
updateAppWidget(context, appWidgetManager, appWidgetId);
}
private void handleTouchWiz(Context context, Intent intent) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int appWidgetId = intent.getIntExtra("widgetId", 0);
int widgetSpanX = intent.getIntExtra("widgetspanx", 0);
int widgetSpanY = intent.getIntExtra("widgetspany", 0);
if (appWidgetId > 0 && widgetSpanX > 0 && widgetSpanY > 0) {
Bundle newOptions = new Bundle();
// We have to convert these numbers for future use
// http://stackoverflow.com/questions/10008521/appwidget-size-calculation
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, widgetSpanY * 74 - 2);
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, widgetSpanX * 74 - 2);
} else {
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, widgetSpanY * 70 - 30);
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, widgetSpanX * 70 - 30);
}
onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
}
}
Related
Devices like Samsung Galaxy S3 working with Android version 4.1.2 has a bug which prevents onAppWidgetOptionsChanged to be called.
So, how can we get information related to changed sizes?
I am adding to Frankish's answer with how I handle receiving this broadcast:
#Override
public void onReceive(Context context, Intent intent) {
// Handle TouchWiz
if(intent.getAction().contentEquals("com.sec.android.widgetapp.APPWIDGET_RESIZE")) {
handleTouchWiz(context, intent);
}
super.onReceive(context, intent);
}
private void handleTouchWiz(Context context, Intent intent) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int appWidgetId = intent.getIntExtra("widgetId", 0);
int widgetSpanX = intent.getIntExtra("widgetspanx", 0);
int widgetSpanY = intent.getIntExtra("widgetspany", 0);
if(appWidgetId > 0 && widgetSpanX > 0 && widgetSpanY > 0) {
Bundle newOptions = new Bundle();
// We have to convert these numbers for future use
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, widgetSpanY * 74);
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, widgetSpanX * 74);
onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
}
}
You may need to change how you handle that bundle if you are using MAX_HEIGHT or MAX_WIDTH which I am not.
I have discovered that you can catch RESIZE action in onReceive function.
if(intent.getAction().contentEquals("com.sec.android.widgetapp.APPWIDGET_RESIZE"))
You'll get the following values from this intent's getExtras() bundle:
int appWidgetId = bundle.getInt("widgetId"); // bundle.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
int widgetSpanX = bundle.getInt("widgetspanx", 4);
int widgetSpanY = bundle.getInt("widgetspany", 1);
Now you can use these values to update the view or store them in a static integer map variable to use in the next onUpdate.
I am using AlarmManager to update my widgets. And I want to stop it if there is no widget on homescreen. But I am facing a problem with detecting if there is no widget on home screen.
As whenever I try to get the AppWidgetIds using this way:
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int[] appWidgetIDs = appWidgetManager
.getAppWidgetIds(new ComponentName(context, Widget.class));
I get the a length of appWidgetIDs while actually there is no widget on homescreen. Why?
Therefore, I would like to know if there is a way to detect that a widget id is exists on homescreen.
Thank you upfront.
Congratulations, you've encountered phantom appwidgets. It appears to be documented on the Android issue tracker. They usually occur when the configuration activity for an appwidget is canceled, though it seems to be through improper implementation of the configuration activity; developers neglect to include the appwidget ID as an extra when setting the activity result to RESULT_CANCELED. (even Google's ApiDemos sample application neglects to do this!)
The proper implementation is like this:
public class AppWidgetConfigActivity extends Activity {
private int appWidgetId;
private Intent resultValue;
protected void onCreate(bundle saved) {
super.onCreate(saved);
// get the appwidget id from the intent
Intent intent = getIntent();
appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
// make the result intent and set the result to canceled
resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
setResult(RESULT_CANCELED, resultValue);
// if we weren't started properly, finish here
if (appwidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish();
}
/* ... */
}
/* ... */
private void finishConfigure() {
/* finish configuring appwidget ... */
setResult(RESULT_OK, resultValue);
}
}
Thus far I know of no way to detect the presence of a phantom appwidget without doing your own bookkeeping. I suggest storing a SharedPreferences value indicating that the configuration activity was not canceled and then querying this value in your other code. You can also use this information to "delete" a phantom widget if you come across one. In your appwidget configuration activity:
private void finishConfigure() {
/* finish configuring appwidget ... */
setResult(RESULT_OK, resultValue);
String key = String.format("appwidget%d_configured", appwidgetId);
SharedPreferences prefs = getSharedPreferences("widget_prefs", 0);
prefs.edit().putBoolean(key, true).commit;
}
Then you can check that you have at least one non-phantom appwidget like so:
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
AppWidgetHost appWidgetHost = new AppWidgetHost(context, 1); // for removing phantoms
SharedPreferences prefs = getSharedPreferences("widget_prefs", 0);
boolean hasWidget = false;
int[] appWidgetIDs = appWidgetManager.getAppWidgetIds(new ComponentName(context, Widget.class));
for (int i = 0; i < appWidgetIDs.length; i++) {
int id = appWidgetIDs[i];
String key = String.format("appwidget%d_configured", id);
if (prefs.getBoolean(key, false)) {
hasWidget = true;
} else {
// delete the phantom appwidget
appWidgetHost.deleteAppWidgetId(id);
}
}
if (hasWidget) {
// proceed
} else {
// turn off alarms
}
Can someone tell me how to check that my widget have been placed on the homescreen?
I have some code in my app that should run only if the widget is placed on the homescreen.
Just saying, but...
int ids[] = AppWidgetManager.getInstance(this).getAppWidgetIds(new ComponentName(this,MyAppWidgetProvider.class));
Toast.makeText(this, "Number of widgets: "+ids.length, Toast.LENGTH_LONG).show();
You need to store that information yourself. I usually use the application preferences, but you could use anything. Generally widgets use services to communicate, so your code that does stuff is likely in a service, but using the preference allows any portion of your app to access this.
In your widget class that extends AppWidgetProvider the onEnabled is called when the widget is put on a homescreen and the onDeleted is (usually) called when it's removed. onDisabled is called when all copies are removed.
So in the code of your widget provider:
#Override
public void onEnabled(Context context) {
super.onEnabled(context);
setWidgetActive(true);
context.startService(new Intent(appContext, WidgetUpdateService.class));
}
#Override
public void onDisabled(Context context) {
Context appContext = context.getApplicationContext();
setWidgetActive(false);
context.stopService(new Intent(appContext, WidgetUpdateService.class));
super.onDisabled(context);
}
private void setWidgetActive(boolean active){
Context appContext = context.getApplicationContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
SharedPreferences.Editor edit = prefs.edit();
edit.putBoolean(Constants.WIDGET_ACTIVE, active);
edit.commit();
}
Elsewhere in code, you would check to see if the widget is active by:
public boolean isWidgetActive(Context context){
Context appContext = context.getApplicationContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getBoolean(Constants.WIDGET_ACTIVE, false);
}
I know it's an old question, but looking at this today I saw that there are a couple of problems with the accepted answer from #larsona1:
if the user cleared the shared preferences - there's still widget, but the app won't know about it.
if the user regret between "add widget" and before pressing "ok" - onEnabled will be called anyway, and a widget will be registered in the home screen even though there is no widget, and no way to remove it later. (it may be a bug in ADT home launcher).
I found a solution to the first problem.
No shared preferences are needed at all, since it's unreliable anyway. It has to be checked in runtime.
// in some class you define a static variable, say in S.java
static boolean sWidgetMayExist = true;
In your widget provider:
// MyAppWidgetProvider.java
// to respond to runtime changes, when widgets are added and removed
#Override
public void onEnabled(Context context) {
super.onEnabled(context);
S.sWidgetMayExist = true;
}
#Override
public void onDisabled(Context context) {
super.onDisabled(context);
S.sWidgetMayExist = true;
}
And, in your service code add this:
AppWidgetManager manager = null;
RemoteViews views = null;
ComponentName widgetComponent = null;
// ..and in your update thread
if (!S.sWidgetMayExist) { return; }
if (manager == null || widgetComponent == null) {
widgetComponent = new ComponentName(c,
MyAppWidgetProvider.class);
manager = AppWidgetManager.getInstance(c);
}
if (manager.getAppWidgetIds(widgetComponent) == null) {
S.sWidgetMayExist = false;
}
#Waza_Be is right as looking at the "AppWidgetIds" list to know the number of active widgets (those installed on your homescreen) is the correct way to know this information.
However, keep in mind that you SHOULD don't have to look at this by yourself.
Check the official Android documentation for best practice about widgets :
https://developer.android.com/guide/topics/appwidgets/index.html#AppWidgetProvider
The right approach is to override only the onUpdate() method and iterate through the list of "active" widgets :
public class ExampleAppWidgetProvider extends AppWidgetProvider {
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
final int N = appWidgetIds.length;
// Perform this loop procedure for each App Widget that belongs to this provider
for (int i=0; i<N; i++) {
int appWidgetId = appWidgetIds[i];
// Create an Intent to launch ExampleActivity
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);
// Tell the AppWidgetManager to perform an update on the current app widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
}
And as your own widget provider overrides AppWidgetProvider, you will NOT go into the onUpdate() method if you have no widgets active on the home screen!
See the onReceive() code of Android AppWidgetProvider that checks already for you that "appWidgetIds.length > 0":
public void onReceive(Context context, Intent intent) {
// Protect against rogue update broadcasts (not really a security issue,
// just filter bad broacasts out so subclasses are less likely to crash).
String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds != null && appWidgetIds.length > 0) {
this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
}
}
}
(...)
}
What about the following:
boolean widgetExists(Context context, int appWidgetId) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
AppWidgetProviderInfo info = appWidgetManager.getAppWidgetInfo(appWidgetId);
return (info != null);
}
From the docs for appWidgetManager.getAppWidgetInfo():
If the appWidgetId has not been bound to a provider yet, or you don't have access to that appWidgetId, null is returned.
I have an Android app widget and I would like to extend it to support several sizes and also re-sizable with Honeycomb.
The problem is that I need to know what is the size of the widget , so I can know how much content I can put in it.
How do I read the app widget size?
I couldn't find anything.
I know it's old question, but there's a newer answer to it (that I believe was not available on the time, as it is API 16 and up only):
you can call:
Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
that will give the same options that is passed to your receiver during
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions)
with this Bundle options you can query for the sizing using the constants:
AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT
AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH
AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT
AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH
Remembering that Samsung likes to be different, so you probably need a special Handler for TouchWiz (code bellow copied directly from my app):
#Override
public void onReceive(Context context, Intent intent) {
if (intent == null || intent.getAction() == null)
return;
// I FUCKING HATE SAMSUNG!
if (intent.getAction().contentEquals("com.sec.android.widgetapp.APPWIDGET_RESIZE") &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
handleTouchWiz(context, intent);
}
super.onReceive(context, intent);
}
#TargetApi(16)
private void handleTouchWiz(Context context, Intent intent) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int appWidgetId = intent.getIntExtra("widgetId", 0);
int widgetSpanX = intent.getIntExtra("widgetspanx", 0);
int widgetSpanY = intent.getIntExtra("widgetspany", 0);
if (appWidgetId > 0 && widgetSpanX > 0 && widgetSpanY > 0) {
Bundle newOptions = new Bundle();
// We have to convert these numbers for future use
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, widgetSpanY * 74);
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, widgetSpanX * 74);
onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
}
}
I have an Android application with a widget, that has buttons. This code works.
The buttons on the widget stop working when something happens, such as changing the language of the phone. I use shared preferences, so if the user reinstalls the app (without uninstalling), the buttons are working again and the settings remain the set ones.
I have noticed the Intents in my AppWidgetProvider class (code beneath this analysis) are not fired appropriately.
I added a Toast message to the Call1 class instantiated from AppWidgetProvider, but it doesn't display.
My UpdateService.java is just getting the set preferences and customizing the widget's appearance, so I don't think it could possibly be related to my issue.
My Main.java file merely consists of spinners and saves shared preferences, which means I select "Computer" in a spinner, so that the "Computer" text appears on the widget. It also does not disappear when I change the language of the phone, and neither do images. Therefore, I believe UpdateService.java must be ok.
Here is the AppWidgetProvider class:
public class HelloWidget extends AppWidgetProvider {
public static String ACTION_WIDGET_CONFIGURE = "ConfigureWidget";
public static String ACTION_WIDGET_CONFIGURE2 = "ConfigureWidget";
public static String ACTION_WIDGET_RECEIVER = "ActionReceiverWidget";
public static String ACTION_WIDGET_RECEIVER2 = "ActionReceiverWidget";
private static final int REQUEST_CODE_FOUR = 40;
private static final int REQUEST_CODE_FIVE = 50;
private static final int REQUEST_CODE_SIX = 60;
private static final int REQUEST_CODE_SEVEN = 70;
private static final int REQUEST_CODE_EIGHT = 80;
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
context.startService(new Intent(context, UpdateService.class));
//Intent widgetUpdateIntent = new Intent(context, UpdateService.class);
//context.startService(widgetUpdateIntent );
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widgetmain2);
//P1 starts Call1.class
Intent configIntent4 = new Intent(context, Call1.class);
configIntent4.setAction(ACTION_WIDGET_CONFIGURE);
PendingIntent configPendingIntent4 = PendingIntent.getActivity(context, REQUEST_CODE_FOUR, configIntent4, 0);
remoteViews.setOnClickPendingIntent(R.id.ImageView01, configPendingIntent4);
//P2 starts Call2.class
Intent configIntent5 = new Intent(context, Call2.class);
configIntent5.setAction(ACTION_WIDGET_CONFIGURE);
PendingIntent configPendingIntent5 = PendingIntent.getActivity(context, REQUEST_CODE_FIVE, configIntent5, 0);
remoteViews.setOnClickPendingIntent(R.id.ImageView02, configPendingIntent5);
//P3 starts Call3.class
Intent configIntent6 = new Intent(context, Call3.class);
configIntent6.setAction(ACTION_WIDGET_CONFIGURE);
PendingIntent configPendingIntent6 = PendingIntent.getActivity(context, REQUEST_CODE_SIX, configIntent6, 0);
remoteViews.setOnClickPendingIntent(R.id.ImageView03, configPendingIntent6);
//P4 starts Call4.class
Intent configIntent7 = new Intent(context, Call4.class);
configIntent7.setAction(ACTION_WIDGET_CONFIGURE);
PendingIntent configPendingIntent7 = PendingIntent.getActivity(context, REQUEST_CODE_SEVEN, configIntent7, 0);
remoteViews.setOnClickPendingIntent(R.id.ImageView04, configPendingIntent7);
//P5 starts Call5.class
Intent configIntent8 = new Intent(context, Call5.class);
configIntent8.setAction(ACTION_WIDGET_CONFIGURE);
PendingIntent configPendingIntent8 = PendingIntent.getActivity(context, REQUEST_CODE_EIGHT, configIntent8, 0);
remoteViews.setOnClickPendingIntent(R.id.ImageView05, configPendingIntent8);
appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
}
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action))
{
final int appWidgetId = intent.getExtras().getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID,AppWidgetManager.INVALID_APPWIDGET_ID);
if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID)
{
this.onDeleted(context, new int[] { appWidgetId });
}
}
else
{
if (intent.getAction().equals(ACTION_WIDGET_RECEIVER))
{
String msg = "null";
try {
msg = intent.getStringExtra("msg");
} catch (NullPointerException e) {
//Log.e("Error", "msg = null");
}
}
super.onReceive(context, intent);
}
}
}
I also have an EditPreferences.java, GlobalVars.java and some other now meaningless classes. The names of the classes speak for themselves.
One other thing. I also have a Widgetmain.java:
public class WidgetMain extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.widgetmain2);
}
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId)
{
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widgetmain2);
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
}
}
Edit: How about this:
When I install this app on my colleague's ZTE Blade the textviews on the widget are not loaded with the appropriate text, just with the one determined in the strings.xml.
When I reinstall the app (without uninstalling), the textviews are loaded and everything is fine. This problem doesn't emerge on my HTC Desire HD.
The textviews are load in the aforementioned UpdateService.java like this (part of the code):
RemoteViews updateViews = new RemoteViews(this.getPackageName(), R.layout.main);
updateViews.setTextViewText(R.id.widget_textview, name);
ComponentName thisWidget = new ComponentName(this, HelloWidget.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
manager.updateAppWidget(thisWidget, updateViews);
Even if "name" is static (e.g. String name="Something"), that textview is still not loaded at the first install.
Try to update the RemoteViews with the click listeners whenever you create new instance by "new RemoteViews". Maybe the RemoteViews are freshly loaded from the XML in some circumstances, therefor the click listeners needs to be re-assigned.
My UpdateService.java is just getting the set preferences and customizing the widget's appearance, so I don't think it could possibly be related to my issue.
It is possible it is related, in as much that you could use it to "refresh" the pending intent. I have a similar issue in my appwidget that an image button stops responding to clicks after some random run time (hours).
I found this thread:
AppWidget Button onClick stops working
And this quote:
The pending intent is "burned" after each use. You need to set it again. Or wait for the widget to get refreshed, then it happens, too, but that's probably not the desired way.
Given that the widget update time normally is set at many hours or days (mine is 86400000 milli seconds) in order to prevent the phone going out of suspend every so many minutes your widget will not often run onUpdate. It is possible that setting the pending intent ALSO in the update service will prevent the problem you describe.Each time the update service runs the pending intent is re-created.
I have today added this possible fix to my appwidget and I have to wait and see if the fix really works, but so far so good.
I added the following code in the update service' loop where it refreshes each widget:
for (int i=0; i<appWidgetIds.length; i++)
{
appWidgetId=appWidgetIds[i];
/* other stuff to do */
RemoteViews views=new RemoteViews(context.getPackageName(), R.layout.example_appwidget);
/* here you "refresh" the pending intent for the button */
Intent clickintent=new Intent("net.example.appwidget.ACTION_WIDGET_CLICK");
PendingIntent pendingIntentClick=PendingIntent.getBroadcast(context, 0, clickintent, 0);
views.setOnClickPendingIntent(R.id.example_appwidget_button, pendingIntentClick);
appWidgetManager.updateAppWidget(appWidgetId, views);
/* then tell the widget manager to update */
appWidgetManager.updateAppWidget(appWidgetId, views);
}
The problem is that you can't do a partiall update for a widget, you must set all the widget features, such as the set of PendingIntent's every time you push a new remoteView. (Partiall updates are only available for API14 and up...).
The reason your widgets are loosing their pendingIntents is that the android system saves the remoteView, and rebuilds your widget with it, in case it resets the widget (shortage of memmory, TaskManager/taskKiller in use, etc...), so you must set all the update code for the widget in the remoteView in your updateService. Otherwise, it's just won't set the pendingIntents again.
So just add the code setting the pendingIntents to the service and your problem will be solved =]
I think the PendingIntents may need a flag passed to them, maybe try changing:
PendingIntent.getActivity(context, REQUEST_CODE, configIntent, 0);
to:
PendingIntent.getActivity(context, REQUEST_CODE, configIntent, PendingIntent.FLAG_UPDATE_CURRENT);
From the PendingIntent documentation, I think code '0' is undefined. In this case FLAG_UPDATE_CURRENT would work best, as you probably want to update the Intent every time the button is clicked.
Given all the information you gave, I'd say your update method is not triggered properly when the preferences are changed.
I expect after so much tests, you have verified your Manifest file contains:
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
Have you confirmed onUpdate ever runs? It seems to me that if reinstalling the application without deinstalling solves your issues, it might be because it forces an update call.
After careful check, it turns out that ScanPlayGames has a point: the official documentation's example uses super.onUpdate(). Note that it uses it at the end of the method, but several examples on Internet state you're better served using it at the start of your method.
I've had that problem for long time. My widget has button #(onUpdate). The widget has a service for updates. The button on the widget stop working when something happens, like: changing the font, etc..
When i re-install the app, the button works again. Finally, I realized that i never called onUpdate in my Service class.
Calling onUpdate from the service class fixed the problem.
If someone still has this problem try setting the attribute android:updatePeriodMillis in your AppWidgetProviderInfo;
The operating system can kill the pending intent for various reasons and your buttons can stop to work. When you set this attribute, you are telling Android when it should call the onUpdate method in the AppWidgetProvider, so all pending intents will be re-created.
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
...
android:updatePeriodMillis="3600000">
</appwidget-provider>