I am not going to delete this question as commons brings up some excellent points below, but I did rework the code and ask the question differently here: How do I retrieve shared preferences data in a Widget Service class without passing in incorrect default values or getting null pointer errors?
I am working on an app that takes a user's input choices and passes them to a widget. It is currently running a service to manage it and it works well, but I cannot figure out how to pass a String from one to the other effectively. Here is my code so far:
//First Widget config is called:
public class WidgetConfig extends Activity{
//Stuff happens here to get data from EditTexts and spinners and converts
//them to strings.
//Eventually a button is pressed which enters all the information:
public void onClick(View v) {
//I have already tried shared preferences like this:
//This was intended to give the shared preferences a unique identifier.
//It does not work for what I am trying to do
String str = Integer.toString(appWidgetId);
sp.putString(editor, str + "::" + "username", user_name);
//The appWidgetID was unique and so I thought it would work as an
//identifier for shared prefs.
//This is the intent for opening the provider
Intent intentUpdate = new Intent(context, MailWidgetProvider.class);
//I also attempted to put items here:
intentUpdate.putExtra("username", user_name);
//I left out the rest of the pending update code as it is irrelevant to this.
}
}
//Next the AppWidgetProvider is called
public class MailWidgetProvider extends AppWidgetProvider {
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
ComponentName thisWidget = new ComponentName(context,
MailWidgetProvider.class);
int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
//This is the intent to open up and run the service
Intent intent = new Intent(context.getApplicationContext(),
MailWidgetUpdateService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds);
context.startService(intent);
}
}
//Service Class
public class MailWidgetUpdateService extends Service {
public void onStart(Intent intent, int startId) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this
.getApplicationContext());
int[] allWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
ComponentName thisWidget = new ComponentName(getApplicationContext(),
MailWidgetProvider.class);
int[] allWidgetIds2 = appWidgetManager.getAppWidgetIds(thisWidget);
//Loop through the IDs
for (int widgetId : allWidgetIds) {
int awid = widgetId;
String str = Integer.toString(widgetId);
String user_name = sp.getString(settings, str + "::" + "chosen_accout_string", "Loading...");
Log.d(TRACKING_USERNAME, user_name);
/*
Issue Here, see explanation below
*/
}
}
How do I retrieve the extras in the Widget Provider class from the widget config class and how do I go about passing them on to the service after receiving them?
You start by not doing much of any of that.
Your AppWidgetProvider is merely one means of updating the app widget contents, one that will specifically be used by Android when your app widget is added and on periodic updates as requested by your app widget metadata. Moreover, bear in mind that an instance of your AppWidgetProvider is used just once and is then discarded.
If you want to update your app widget in other places, go update the app widget, by creating the RemoteViews and giving them to an AppWidgetManager. Your AppWidgetProvider has nothing to do with it. To quote the documentation:
When an App Widget uses a configuration Activity, it is the responsibility of the Activity to update the App Widget when configuration is complete. You can do so by requesting an update directly from the AppWidgetManager.
If you want to have a common implementation of the update-the-app-widget logic, put that is some common class that is used by your configuration Activity, your AppWidgetProvider, and anything else that needs to update the app widget contents.
So, when the user configures the app widget through the activity, you need to:
update the app widget yourself via the AppWidgetManager, and
hold onto the configuration data (in a database, SharedPreferences, or other sort of file) so that it can be used for future updates
So far, all the AppWidgetProvider code example I had seen, they placed UI event handler register code in onUpdate.
However, isn't this is some how inefficient? As I thought UI event handler registration just need to be done 1 time.
onUpdate will always be triggered repeatably.
Is there any more efficient way?
public class MyWidgetProvider extends AppWidgetProvider {
private static final String ACTION_CLICK = "ACTION_CLICK";
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
...
// Register an onClickListener
Intent intent = new Intent(context, MyWidgetProvider.class);
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.update, pendingIntent);
appWidgetManager.updateAppWidget(widgetId, remoteViews);
}
}
}
Let me put it in this way.
Remote views are not the real views, but rather a set of rules, which is used to create real views. Then you call updateAppWidget(), then this set of rules gets sent to Android, and Android creates new views by applying these rules. OnClickPendingIntent is one of those rules. If it's there, then it gets applied and you have a listener set.
If you create a new instance of remoteViews, then you have to provide a OnClickPendingIntent to this instance too. If you don't, then there will be no listener registered and you won't get a callback. In this regards, you do not set listener twice or multiple times at the same instance - because you always re-create remoteViews too - and this is optimal.
Android might apply additional optimization by reusing already existing views, if remote views were not changed. But this is something out of your control.
I will make it simple. I have my widget code. My widget layout contains a linear layout with one button in it. In my widget code, I initialize a String List with some values in it.
When I click the button in my widget, I have to update my List with some more values.
So, this is my code,
List<String> myList = null;
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
myList = new List<String>();
myList.add("1");
myList.add("2");
...
Intent intent = new Intent(context, getClass());
intent.setAction("CALL_UPDATE");
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.button, pendingIntent);
....
}
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("CALL_UPDATE")) {
Toast.makeText(context, "Intent received"+myList , 2000).show();
}
}
When the button is clicked, the broadcast is properly received by onReceive(). The problem is, on onReceive(), I see my list as null instead of some values in it as some string objects were added earlier.
Can anyone help?
Thx!
Rahul.
Documentation says:
This has important repercussions to what you can do in an
onReceive(Context, Intent) implementation: anything that requires
asynchronous operation is not available, because you will need to
return from the function to handle the asynchronous operation, but at
that point the BroadcastReceiver is no longer active and thus the
system is free to kill its process before the asynchronous operation
completes.
In particular, you may not show a dialog or bind to a service from
within a BroadcastReceiver. For the former, you should instead use the
NotificationManager API. For the latter, you can use
Context.startService() to send a command to the service.
And:
onReceive() is normally called within the main thread of its process,
so you should never perform long-running operations in it (there is a
timeout of 10 seconds that the system allows before considering the
receiver to be blocked and a candidate to be killed). You cannot
launch a popup dialog in your implementation of onReceive().
Edit:
AppWidgetProvider is a BroadcastReceiver and it's instance (and so it's fields) will be deleted after it's lifcycle.When you create a new instance of widget in HomeScreen,onUpdate and onReceive of AppWidgetProvider invoke and list of this instance of AppWidgetProvider is not null.But after invoking onReceive (for example 10 seconds)this instance will delete.When you click on button ,second instance of AppWidgetProvider will be create and it's list is null.
You can save your list public static field of a class and retrieve it when you need.
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>
I have a widget that launches an activity, but when the activity finishes using the finish() I don't know how my widget can know about it since I can't override onActivityResult() which seems like the only way to listen when an activity closes...
Anyone know of another way to listen for when an Activity closes when it is a widget that launches the Activity?
In case it helps, here is the code I'm using in my widget to launch the Activity
#Override
public void onUpdate(Context context,
AppWidgetManager appWidgetManager, int[] appWidgetIds) {
Intent i = new Intent(context, ChooseContact.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context,0,i,0);
RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.main);
updateViews.setOnClickPendingIntent(R.id.button, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetIds, updateViews);
// Not really necessary, just a habit
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
As you said, it's better for the Activity to update the widget. You can override the onStop method of the Activity, and send an Intent to the widget to ask it to redraw... something like this:
#Override
protected void onStop() {
super.onStop();
// save settings
// ...
// update widget
Intent intent = new Intent("com.mywidget.action.SETTINGS_CHANGED");
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { widgetId });
intent.setData(Uri.withAppendedPath(Uri.parse(App.URI_SCHEME + "://widget/id/"),
String.valueOf(widgetId)));
sendBroadcast(intent);
}
It looks like I'm approaching it the wrong way. According to the docs:
When an App Widget uses a configuration Activity, it is the responsibility of the Activity to update the App Widget when configuration is complete.
http://developer.android.com/guide/topics/appwidgets/index.html#Configuring
So it looks like I'll update the widget from the activity.