Android Widgets: Not able to retain old values - android

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.

Related

Home widget - Is it inefficient for UI event handler registration be done multiple times through onUpdate

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.

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.

How does an android activity interact with app-widget (Home screen widgets)?

If I have an activity, so how can I use some of its methods (functionalities) in android widgets. Do I have to recreate the logic? What are the ways of interaction? Through intent or through service? Please please reply.
Update: I have a .java class that implements Parcelable and contains the method that returns the list. I want that list in my widget.
public Stores getStoreListings() {
Log.i("List val","mStoreListings");
return mStoreListings;
}
Can I use Intent in my app widget to get this method or variable? This is not an activity..
UPDATE2: Using Async task, its not working also..Where I am going wrong?? PLease help..
public class ListViewWidget extends AppWidgetProvider{
#Override
public void onUpdate(Context context,AppWidgetManager appWidgetManager, int []appWidgetIds){
super.onUpdate(context, appWidgetManager, appWidgetIds);
RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.list_layout);
ComponentName thisWidget = new ComponentName(context,ListViewWidget.class);
String url=String.valueOf("https://api.paypal.com/v1/stores?view=local&lat=37.3756096&lng=-121.9239449&radius=50&count=20&start_id=1&country_code=US");
FetchTask fetchTask=new FetchTask();
//fetchTask.appWidgetManager=appWidgetManager;
//fetchTask.onPostExecute(updateViews,context);
fetchTask.execute();
//context.startService(new Intent(context,UpdateService.class));
}
public static class FetchTask extends AsyncTask<URL,Integer,Stores>{
String text=null;
#Override
protected Stores doInBackground(URL... arg0) {
// TODO Auto-generated method stub
HttpClient httpClient=new DefaultHttpClient();
HttpContext localContext=new BasicHttpContext();
HttpGet httpGet=new HttpGet("https://api.paypal.com/v1/stores?view=local&lat=37.3756096&lng=-121.9239449&radius=50&count=20&start_id=1&country_code=US");
Stores newList=new Stores();
try{
HttpResponse response=httpClient.execute(httpGet,localContext);
HttpEntity entity=response.getEntity();
text=String.valueOf(entity);
//assign the list to the the correct entity type
//newList=..;
Log.i("Inside on update with Http call","Text"+text);
}
catch(Exception e){
e.printStackTrace();
}
return newList;
}
protected void onPostExecute(RemoteViews updateViews,Context context){
Intent intent=new Intent("android.appwidget.action.APPWIDGET_UPDATE");
PendingIntent pendingIntent=PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
updateViews.setTextViewText(R.id.text_view, text);
updateViews.setOnClickPendingIntent(R.id.next, pendingIntent);
// Push update for this widget to the home screen
ComponentName thisWidget = new ComponentName(this, ListViewWidget.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
manager.updateAppWidget(thisWidget, updateViews);
}
}
}
You can use the same server side logic, but you probably want to copy and paste it to a new service.
App Widgets work differently with their lifecycle. If you want your app widget to update periodically, you set that in your app widget config xml. Android will then call your provider when it is time to update the data.
At that point is where you would start your service, where you can reuse the logic to fetch whatever data you need. The only different is once the fetching of the data is done, it needs to be sent to the widget provider and not the activity.
Thats a basic overview of widgets, but to answer your question, it is best to re implement your logic to suit the app widget lifecycle.
Ok, you have activity and service (or AsyncTask) for now, right? What athor is trying to say - your data-fetching logic should be in Service or AsyncTask. But according to AppWidget lifecycle you need to use Service to fetch new data on schedule and to update your widget views (via RemoteViews).
Thats where you can reuse your data-fetching logic - for example, if you had AsyncTask in your activity - you can use instance of same AsyncTask in this new Service.
UPD.
No. Usually, you should start your own service class derived from IntenService (for example) with intent from your widget. Then in service you can just simply get all your data in the same way you did it before.
Check this: AppWidgetProvider extends BroadcastReceiver class.
"
A BroadcastReceiver object is only valid for the duration of the call to onReceive(Context, Intent). 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
"
More details on http://developer.android.com/reference/android/content/BroadcastReceiver.html

send broadcast with combination of localbroadcastmanager sendorderedbroadcast

I'm wanting to implement what CommonsWare describes on this blog post: http://commonsware.com/blog/2010/08/11/activity-notification-ordered-broadcast.html. The post makes sense, and I was able to browse the example source here: https://github.com/commonsguy/cw-advandroid/tree/master/Broadcast.
What I'm curious about is if calling LocalBroadcastManager.getInstance(UnzipService.this).sendBroadcast(broadcast); inside of a service will still be picked up by a broadcast receiver of the type you define in your manifest.
In case what I'm asking isn't clear, what I'm trying to do is use the LocalBroadcastManager because the broadcasts from my service don't necessarily need to be seen system wide and I'd rather keep them private if possible, but I also want to display notifications if the user closes my app and the service is still running. Is there a way to combine both of those capabilities without sending a broadcast twice inside of the service?
(What I don't want to have to do) like:
LocalBroadcastManager.getInstance(UnzipService.this).sendBroadcast(broadcast);
sendOrderedBroadcast(broadcast);
What I'm curious about is if calling LocalBroadcastManager.getInstance(UnzipService.this).sendBroadcast(broadcast); inside of a service will still be picked up by a broadcast receiver of the type you define in your manifest.
No. LocalBroadcastManager only works with receivers registered with the LocalBroadcastManager singleton itself. Moreover, LocalBroadcastManager does not support ordered broadcasts, last I checked.
what I'm trying to do is use the LocalBroadcastManager because the broadcasts from my service don't necessarily need to be seen system wide and I'd rather keep them private if possible
So long as you are not using an <intent-filter> on your BroadcastReceiver in the manifest, and therefore are using an explicit Intent as the broadcast itself, your broadcast will only be seen by yourself and the bit of the OS that manages broadcasts. Other apps will not be able to spy upon it.
If you only have 2 objects that might handle your broadcast (in your case an Activity and a notifications controller), you can achieve the behavior of a ordered broadcast using only the LocalBroadcastManager.
The general idea is:
Set up your Service so that it broadcasts an Intent to your Activity with a particular action when you want to display your result
In your Activity create a BroadcastReceiver that handles your Service result Intent, and register it on the LocalBroadcastManager with an IntentFilter using the action from step 1
In your Service, when the results are available, try to send the result Intent using LocalBroadcastManager.getInstance(Context).sendBroadcast(Intent) this method returns a boolean that indicates if the broadcast has been sent to at least one receiver. If this boolean is false, it means that your Activity didn't handle your broadcast and you should show a notification instead.
In your service:
public UnzipService extends IntentService {
public static final String ACTION_SHOWRESULT = UnzipService.class.getCanonicalName() + ".ACTION_SHOWRESULT";
#Override
protected void onHandleIntent(Intent intent) {
Thread.sleep(500); // Do the hard work
// Then try to notify the Activity about the results
Intent activityIntent = new Intent(this, YourActivity.class);
activityIntent.setAction(ACTION_SHOWRESULT);
activityIntent.putExtra(SOME_KEY, SOME_RESULTVALUE); // Put the result into extras
boolean broadcastEnqueued = LocalBroadcastManager.getInstance(this).sendBroadcast(activityIntent);
if (!broadcastEnqueued) { // Fallback to notification!
PendingIntent pendingIntent = PendingIntent.getActivity(this, (int) System.currentTimeMillis(), activityIntent, PendingIntent.FLAG_UPDATE_CURRENT);
((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
.notify(SOME_ID, new NotificationCompat.Builder(this)
.setContentIntent(pendingIntent)
.setTicker("results available")
.setContentText("results")
.build());
}
}
}
In your Activity:
public YourActivity extends Activity {
private BroadcastReceiver resultReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
processResult(intent); // Results Intent received through local broadcast
}
}
private IntentFilter resultFilter = new IntentFilter(UnzipService.ACTION_SHOWRESULT);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate();
Intent intent = getIntent();
if (UnzipService.ACTION_SHOWRESULT.equals(intent.getAction())) {
// The Activity has been launched with a tap on the notification
processResult(intent); // Results Intent contained in the notification PendingIntent
}
}
#Override
protected void onResume() {
super.onResume();
LocalBroadcastManager.getInstance(this)
.registerReceiver(resultReceiver, resultFilter);
}
#Override
protected void onPause() {
LocalBroadcastManager.getInstance(this)
.unregisterReceiver(resultReceiver);
super.onPause();
}
private void processResult(Intent intent) {
// Show the results from Intent extras
}
}
This should be a complete working example.
I hope this helps who is trying to implement ordered broadcasts with LocalBroadcastManager from support library!
I understand you want to achieve the following:
"I have an event that occurs in the background. I want to update my activity, if the activity is on the screen. Otherwise, I want to raise a Notification." (#TheCommonsBlog)
You can achieve this behaviour by implementing a ResultReceiver.
Examples Restful API service and
http://itekblog.com/background-processing-with-intentservice-class/
What you basically do is instance a ResultReceiver in your Activity and pass it to the Service like a Parcelable parameter through an intent. Then, each time your service whats to update the UI, the service verifies the ResultReceiver object for NULL. If not NULL, you update the Ui via the onReceiveResult interface. Else, you raise a notification. When your activity dismisses, make sure you set the ResultReceiver on the Service to NULL.
Hope it helps.
PS: IMO, broadcasts are too much work and hard to control.
Use LocalBroadcastManager and broadcasts become easy to use.
I am not in favor of updating an Activity if an event occurs in the background. The user might already be doing something else in the Activity. Seems to me that a Notification is sufficient; it's always visible and remains until the user dismisses it. Gmail and Gcal work like this; Gmail doesn't update the current screen if a new mail comes in. If you want to know how to handle the task flow for handling a notification when the user is already in the app, see the Notifications API guide and also the [Notifying The User2 training class.

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!

Categories

Resources