Android - widget breaks after launcher reload/crash - android

I've made an Android home screen widget which displays some text and has 2 click listeners which I have working under normal operation, no problems. That is until HTC sense/launcher decides to reload, which it does most days. After the reload one of the click listeners stops working and can cause the widget to Force Close.
onClickListener 1 = starts an activity and passes it some text strings
onClickListener 2 = Triggers FORCE_WIDGET_UPDATE
After a launcher reload, the activity still trigures fine, but the widget update fails.
Here is the essence of the update code.
public static String updateString = "org.software.appname.FORCE_WIDGET_UPDATE";
public void onReceive(Context ctx, Intent intent){
showIntent=intent.getAction();
Log.d(TAG, showIntent);
if (updateString.equals(intent.getAction())){
Log.d(TAG, "onReceive, Force");
AppName.updateViews = new RemoteViews( ctx.getPackageName(),R.layout.main );
<Code to update text, update widget view and load new intents>
ComponentName me = new ComponentName( ctx, AppName.class );
AppWidgetManager.getInstance( ctx ).updateAppWidget( me, updateViews );
} else super.onReceive(ctx, intent);
I've tried moving the onRecieve call to the super class inside the if statement, but this causes the app to force close as soon you the widget is dropped onto the home screen.
I don't see anything in logcat when clicking the button, when I normally would, sometimes it force closes sometimes it doesn't. I can re-create the problem in the desktop simulator by force closing the stock launcher in application setting.
Thanks in advance for your help! :-)
Edit:
I've tried changing things around by passing appWidgetIds[] in the intent and then triggering the onUpdate within onReceive, but this doesn't seem to do anything. I get the message in logcat to say it's registering the click, but it doens't seem to be triggering onUpdate.
public void onReceive(Context ctx, Intent intent){
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(ctx);
int[] appWidgetIds = intent.getIntArrayExtra("appWidgetIds");
onUpdate(ctx, appWidgetManager, appWidgetIds);
Log.d(TAG, "onReceive log");
}

Related

AppWidget: instance don't be shown after config activity is finished on some devices

I encountered wierd behaviour on some devices when add new instance of appwidget on the home screen.
I've got AppWidget application with configuration activity. As it was said in tutorial update of appwidget I have to do by myself.
public static void updateWidgetAndSendIntent (Activity activity, int mAppWidgetId, boolean isUpdate) {
updateWidgets(activity);
if (!isUpdate) {
Intent resultIntent = new Intent();
resultIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
activity.setResult(Activity.RESULT_OK, resultIntent);
activity.finish();
}
}
public static void updateWidgets(Context context) {
new ManagerUpdateWidget(context).updateAllWidgetInstances();
}
ManagerUpdateWidget.java
public void updateAllWidgetInstances() {
Bitmap widget = getCustomView(context);
for (int widgetId : appWidgetIds) {
updateCurrentInstance(widgetId, widget);
}
}
The fact is that on Samsung Galaxy Note 2 (GT-N7100) with Android 4.4.2 everything is Ok, but on Samsung Galaxy Note 3 (SM-N900) with Android 5.0 I've got "phantom" appwidget, not real appawidget on the home screen but the "phantom" ones when you cann't see appwidget on screen but inside your app appwidget exists with ID and regulary updates.
I've tested my app on genymotion emulators (Android 4.3 and 5.0) but everything was Ok too.
Please, suggest me how to fix this wierd bug.
Dealing with "phantom" widgets it's a nightmare. Can you update all the instances using updateAppWidget (ComponentName provider, RemoteViews views)?
According to the documentation, this method is going to set the RemoteViews to use for all AppWidget instances for the supplied AppWidget provider.
AppWidgetManager manager = AppWidgetManager.getInstance(context);
ComponentName component = new ComponentName(context, MyAppWidgetProvider.class);
manager.updateAppWidget(component, remoteViews);

Android widget won't switch layouts more than once

I'm adding a widget to an old app which I'm updating from a service I'm using to poll for data in the background (on an alarm). I update the widget every time the service gets a result. This is currently working correctly.
// Called from inside my service when it has results
private void updateWidget(List<Earthquake> earthquakes) {
AppWidgetManager manager = AppWidgetManager.getInstance(this);
int[] appWidgetIds = manager.getAppWidgetIds(new ComponentName(this, WhatsShakingWidgetProvider.class));
if (appWidgetIds == null || appWidgetIds.length == 0)
return;
Earthquake earthquake = earthquakes.get(0);
RemoteViews views = new RemoteViews(getPackageName(), R.layout.widget_detail);
// Update views
views.setTextViewText(R.id.widget_detail_latest_magnitude, earthquake.getFormattedMagnitude());
// etc...
// Update each widget
for(int appWidgetId : appWidgetIds) {
manager.updateAppWidget(appWidgetId, views);
}
}
This polling service is optional; it can be turned on or off in the app's settings.
If the service is off when the user adds the widget, the widget_error layout is shown, as expected. The user can tap on the widget to enter the settings and turn the background updates on. When they do this (turn the setting on or off), I broadcast ACTION_APPWIDGET_UPDATE. The widget enters onUpdate correctly, and is updated correctly by the service the next time it runs (I've set it up so the widget triggers a service call in onUpdate - see below).
The widget does not correctly display the widget_error layout when the service becomes disabled after being enabled - it leaves the old layout in place, even though all the disabled-case code is run.
This is the code that gets called when the user toggles the setting (Source):
// If our user has widgets, we should update those - let the widget do the updating depending on the prefs, though.
Intent intent = new Intent(this, WhatsShakingWidgetProvider.class);
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
// Use an array and EXTRA_APPWIDGET_IDS instead of AppWidgetManager.EXTRA_APPWIDGET_ID,
// since it seems the onUpdate() is only fired on that:
int[] ids = { R.xml.widget_info };
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
sendBroadcast(intent);
And this is the code in onUpdate which should be updating the widgets, but isn't:
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean backgroundUpdatesEnabled = prefs.getBoolean(PreferenceActivity.KEY_PREF_ALLOW_BG_NOTIFICATIONS,
DefaultPrefs.BG_NOTIFICATIONS_ENABLED);
if (!backgroundUpdatesEnabled) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_error);
// Update click to take to preferences
Intent intent = new Intent(context, PreferenceActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.widget_error_parent_container, pendingIntent);
// Update each widget
appWidgetManager.updateAppWidget(appWidgetIds, views);
} else {
// Let's get some data for the user! Service does the work of updating the views.
WakefulIntentService.sendWakefulWork(context, GeonetService.class);
}
}
There are no errors logged in Logcat. Stepping through this, I correctly enter each part of the if when expected (that is, if the user turned the setting off, then I create RemoteViews views as widget_error, otherwise I start the service).
Why does the widget_error layout display correctly the first time through onUpdate, but not when the user enables, then disables, the background update setting?
I've tried wrapping this in a RelativeLayout and setting the visibility of the error message/the content, but that exhibited the same behaviour - I couldn't get the error message to show back up after initially hiding it.
I ended up duplicating the code in two places (the preferences activity and the widget provider) and it worked. The only variable appears to be the Context object.
It appears that for some reason the Context instance you get in the AppWidgetProvider (that is, in onUpdate) only works the first time - or, doesn't work when I send the broadcast myself. I'm not sure why.
I pulled my duplicated code out to a separate class and just pass in the Context instance I have available, whether it's the Service, an Activity, or the AppWidgetProvider (which is a BroadcastReceiver). This correctly updates the widget, and I can call it from anywhere I have a Context.
Source is available here.

AppWidget does not reliably update upon call updateAppWidget()

I'm creating a fairly common use case of AppWidget on Android.
AppWidgetProvider calls onAppWidgetOptionsChanged() (stretchable widget) and onUpdate() (timed)
From those methods I start an IntentService. Case coming from options changed I pass the new size in the Intent.
The service contacts a web-service, builds the RemoteViews and calls updateAppWidget()
my main test device is a Nexus 7 (2012) running stock 4.3 (stock Launcher)
The widget does not use RemoteViewFactory and does not user AlarmManager. It's a static view with a constant time defined in XML.
It works most of the times, but sometimes the call to updateAppWidget() is completely ignored by the Launcher and no update happens on the screen. If I force close the launcher, clear it caches and re-size the widget (forcing an update) then it updates.
I believe there's something to do with frequency of update because I tricked up some stuff in the IntentService to, whenever it's resizing, only call to the last intent (when the user stops messing with the widget) and it soften a bit the issue.
Let's show some simplified code (it's very standard, i believe):
public class AlbumWidgetService extends WidgetUpdateIntentService {
#Override
protected void onHandleIntent(Intent intent) {
// get's widgetID or array of IDs and pass to 'doTheJob'
}
private void doTheJob(int appWidgetId, int heightInDp, int widthInDp) {
// ...
// here goes code with pre calculations and get data
// ...
// create Intent and PendingIntent with some extras
Intent intent = ... etc
PendingIntent pi = PendingIntent.getActivity( ... etc
// get url for some images
List<String> imageFilenames = getImagesFilename(albumId, totalImages);
// Create the remote view
RemoteViews views = new RemoteViews(getPackageName(), R.layout.album_widget);
// ...
// here goes a bunch of code that load bitmaps from the URLs
// set text and colors in the remote view
// put ImageViews into the remote view, etc
// ...
try {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
appWidgetManager.updateAppWidget(appWidgetId, views);
Log.d(this, "Updating the widget id " + appWidgetId);
} catch (Exception e) {
// this exception happens if the RemoteView is too big, have too many bitmaps.
// I'm already minizing this to happen with the pre calculations, but better safe than sorry
Log.e(this, "Failed to update the widget id " + appWidgetId, e);
}
}
as I said, the thing mostly works (I can see the Log and I can see the on-screen result. But every once in a while it does not update after a resize, even thou I can see the Log and it did not crashes or anything.
ideas?

android widget fetching data with service, cannot work with more than one at once

My widget gets data from the internet every 3 minutes, some are displayed directly on the widget and others are stored in SharedPreferences so when the user taps on the widget that information appears as a dialog. When having more than one widget running, no matter which widget I click the log says the appWidgetId comes from one of them always
My problem seems to be the way I'm declaring the widget's setOnClickPendingIntent(). I'm doing this inside the service, right before fetching the data and since the same service is run by every (widget) AlarmManager, every widget gets the PendingIntent from the last service ran.
public class WidgetService extends Service
{
#Override
public void onStart(Intent intent, int startId)
{
Intent intentUmbrales = new Intent(context, LaunchUmbralesDialog.class);
intentUmbrales.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent pendingIntentUmbrales = PendingIntent.getActivity(context,0,intentUmbrales,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.widget);
// views.setOnClickPendingIntent(R.id.energia_widget, pendingIntentImei);
views.setOnClickPendingIntent(R.id.imageLogo_widget, pendingIntentUmbrales);
//..then I fetch data, store the rest in SharedPreferences and update widget remoteViews
}
}
How can I avoid this? How can I make an individual "button" for each widget with getting them overlapped? Also note that I've already tried to declare those PendingIntents in the AppWidgetProvider's onUpdate() method (inside a loop for every appWidgetId from the array given by the method)
Thanks in advance!
Regards, Rodrigo.
When declaring the .setOnClickPendingIntent() first add to the Intent
Uri data = Uri.withAppendedPath(
Uri.parse(URI_SCHEME + "://widget/id/")
,String.valueOf(appWidgetId));
intent.setData(data);
so that each widget gets a unique ID and they don't get messed up!

Android widget buttons stop working

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>

Categories

Resources