I have a widget that displays an analog clock. What I would like is for the the widget to write to a database the time when a user clicks on the widget. I've already got the databaseHelper class and have an Activity that displays a screen showing the current date and time and writes the time to a database.
I followed the tutorial here: Analog Clock Tutorial and ended up with this:
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action))
{
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);
this.mIntent = new Intent(context, AskTheTime.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, mIntent, 0);
views.setOnClickPendingIntent(R.id.Widget, pendingIntent);
AppWidgetManager.getInstance(context).updateAppWidget(intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS), views);
}
}
The AskTheTime class extends activity and logs to the database in onCreate(). But this means that it displays only the time when the widget was started - not when it was clicked. (I guess this is because I'm starting a pendingIntent) I'm not sure If I should put the database write in another method or if I should be using Intents to do this sort of thing from widgets. Any pointers would be great! I've look at several tutorials for intents but none of them seem very clear to me.
When your widget is pressed, the OS activates the pending intent you set on the widget.
It looks like you are opening an activity AskTheTime when the widget is pressed.
So the problem is that AskTheTime might have already been created when the widget is pressed, so the onCreate() isn't called again.
What you could do is try doing your logging in onStart() or onResume() instead of, or in addition to, onCreate() inside your activity.
From the documentation regarding onStart:
Called when the activity is becoming
visible to the user.
And onResume:
Called when the activity will start
interacting with the user. At this
point your activity is at the top of
the activity stack, with user input
going to it.
Related
I am making a home screen App Widget for my Android app. In that App Widget there is a ListView. That ListView has a custom layout which has a TextView & an ImageButton. I am setting a PendingIntent on that ImageButton to start the service as follows:
Intent intent = new Intent(mContext, MyService.class);
intent.putExtra(MyConstants.MY_EXTRA, c.getString(c.getColumnIndex(MyColumns.MY_COLUMN_TITLE)));
intent.setAction(MyConstants.MY_ACTION);
PendingIntent pendingIntent = PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
rv.setOnClickPendingIntent(R.id.MyImageButton, pendingIntent);
in getViewAt method of RemoteViewsFactory. But on clicking the ImageButton the service does not start.
I have put an intentfilter with MY_ACTION for MyService. And also tried without it which did not make things work.
Anyone knows how to start service on click of a button inside a list view item in home screen App Widget in Android?
Everything works fine i.e. the ListView is populated perfectly from SQLite database.
From the android javadoc
public void setOnClickPendingIntent
Equivalent to calling
setOnClickListener(android.view.View.OnClickListener) to launch the
provided PendingIntent. When setting the on-click action of items
within collections (eg. ListView, StackView etc.), this method will
not work. Instead, use {#link
RemoteViews#setPendingIntentTemplate(int, PendingIntent) in
conjunction with RemoteViews#setOnClickFillInIntent(int, Intent).
In your update method of your AppWidgetProvider you have to add
final PendingIntent clickPendingIntentTemplate = PendingIntent.getService...
views.setPendingIntentTemplate(R.id.widget_list, clickPendingIntentTemplate);
and in the getViewAt method of your RemoteViewsFactory :
final Intent fillInIntent = new Intent();
fillInIntent.putExtra(...);
views.setOnClickFillInIntent(R.id.widget_list_item, fillInIntent);
Is there any work around to call an activity directly from a button on a widget? Like a beautiful button to launch the app.
From the doc and some answers here, views.setOnClickPendingIntent is the only way and that requires a service. But I don't need a service cause I'm not really updating the widget!
Actually, my original task is quite simple. I want an icon on home screen that calls an activity, but I don't want that icon appears in app-drawer. I know I can put a lot of activity icons in app-drawer.
You dont need a service for that.
You should just setOnClickPendingIntent in the onUpdate method in your AppWidgetProvider.
here are some links that show how it can be done, they are basically the same with some variations, you should try them out:
Start activity by clicking on widget
Launching activity from widget
How do I run an Activity from a button on a widget in Android?
How Do I Launch an Activity with Home Widget
Like i said there is no need for a service.
here is the sample code i used:
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds){
RemoteViews remoteViews=new RemoteViews(context.getPackageName(), R.layout.testwidget);
//Intent set with the activity you want to start
Intent intent = new Intent(context, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
remoteViews.setOnClickPendingIntent(R.id.textView1, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
}
My widget is comprised of 2 buttons and a listview displaying data. Most times, when the widget provider's onUpdate method is called, everything loads normally and everyone is happy.
However I've noticed sometimes after the update method is called, the widget just completely fails to load its data. The listview is empty, and all of the buttons are non-responsive. It's as if I initialized the layout into the widget, but none of the pending intents nor the adapter for list were set.
I logged everything and found that this isn't the case. The pending intents ARE created, as is the list adapter, every time, including the random time when it fails. For a long time I thought it had to do with how the list data is populated into the adapter, but seeing it work every single time, coupled with the fact that the button intents don't work as well leads me to believe the method updateAppWidget(ComponentName, RemoteViews) is what is failing. However, there are no error stacks to help me confirm this.
Here is the code which runs in a separate service called by the AppWidgetProvider's onUpdate method:
#SuppressWarnings("deprecation")
private void updateWidget(int[] ids) {
AppWidgetManager widgetManager = AppWidgetManager.getInstance(this);
int[] widgetIds = widgetManager.getAppWidgetIds(new ComponentName(this, WidgetReceiver.class));
for (int currentWidgetId : widgetIds) {
RemoteViews widget = new RemoteViews(this.getPackageName(), R.layout.widget_layout);
Intent intent = new Intent(this, WidgetService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, currentWidgetId);
intent.putExtra("random", randomNumber);
randomNumber++;
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
widget.setRemoteAdapter(currentWidgetId, android.R.id.list, intent);
Intent clickIntent = new Intent(this, DetailsActivity.class);
PendingIntent pending = PendingIntent.getActivity(this, 0, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
widget.setPendingIntentTemplate(android.R.id.list, pending);
Intent settingsIntent = new Intent(this, WidgetSettingsActivity.class);
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent settingsPending = PendingIntent.getActivity(this, 0, settingsIntent, PendingIntent.FLAG_UPDATE_CURRENT);
widget.setOnClickPendingIntent(R.id.widget_settings, settingsPending);
Intent mainIntent = new Intent(this, MainActivity.class);
PendingIntent mainPending = PendingIntent.getActivity(this, 0, mainIntent, PendingIntent.FLAG_UPDATE_CURRENT);
widget.setOnClickPendingIntent(R.id.widget_logo, mainPending);
ComponentName widgetCompName = new ComponentName(this, WidgetReceiver.class);
widgetManager.updateAppWidget(widgetCompName, widget);
}
}
What is most frustrating about this bug is that I can't reliably recreate it. Sometimes I get (un)lucky and it shows it ugly head, other times (the majority of the time) it works perfectly.
Another thing that I thought was interesting is that I've seen the same exact problem in Facebook's widget. Due to NDA I can't show a screen of my app when it fails, but I can show a screen of Facebook's identical problem:
When Facebook's widget looks like this, it has the exact same issues as mine. No data loaded, all buttons are unresponsive.
For both Facebook and my widget, a subsequent update interval will usually make the problem go away.
As much as I appreciate that I'm not the only one who is running into this, I still don't want to release until I fix this. Has anyone here run into this, and better yet found a solution, or even a cause of the problem? Thanks for helping.
EDIT: Something interesting. I ran an experiment where I set the update interval to a full day rather than every 30 minutes. My hypothesis was that maybe the update method wasn't the cause, it was something else which was causing the widget to become blank and unresponsive.
Sure enough, after about 2 hours, I checked my phone and the widget was dead, despite no update method being called. This would lead me to believe that something else is causing this problem, NOT widgetManager.updateAppWidget(widgetCompName, widget); like I previously thought.
I know that a configuration change can cause the widget to be rebuilt, and thus it is possible that it can fail. However, I already use the Service class's onConfigurationChanged method to reload the widget if necessary. Is there another case like configuration change which can cause the widget to destroy and recreate itself?
After a lot of blood, sweat, and tears I found the solution. I am going to hold off on confirming this answer for a few days to make sure that the error doesn't pop back up, but after a lot of observation I think it is resolved.
So I was right in my edited comments in the original question. It wasn't the update method of the AppWidgetManager that caused the problem, but rather some other Android process which caused the app widget to recreate itself. Unfortunately I couldn't isolate that trigger, but I did find a solution.
In my RemoteViewsFactory class (basically the wrapper which is used to load the data set), I had a block of code that looked like this:
RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.widget_layout);
if(mItems.size() == 0)
views.setViewVisibility(R.id.widget_empty, View.VISIBLE);
else
views.setViewVisibility(R.id.widget_empty, View.GONE);
AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
ComponentName widgetCompName = new ComponentName(mContext, WidgetReceiver.class);
manager.updateAppWidget(widgetCompName, views);
Basically, if the list was empty, I showed a Loading message that took up the entire area where the listview is located. If the list wasn't empty, I'd just hide that message.
So this is what was happening: When the factory was destroyed (presumably for memory purposes) the widget itself was not. So all of the code which sets the intents for the data and stuff was not run. However, the factory was recreated, which ran that block of code which updated the app widget with the new remote views object. This remote views object didn't do any of the methods that you see in my onUpdate() method I originally posted, so all of its features didn't work.
Moral of the story: DON'T use updateAppWidget in your RemoteViewsFactory class! Now it may work if you run all the necessary lines, but I can't confirm that.
There have been some similar issues discussed here, but my situation does work some of the time. I am developing a widget that when clicked should launch an activity that's part of the same package. This same activity can also be launched by a notification that may be posted. The widget updates and notification posting are done by a Service in the package. Here is the method that's called to issue the PendingIntent:
// Get pending intent for widget or notification
private PendingIntent getPendingIntent(int widgetId, int extraData) {
Intent clickIntent = new Intent(mCtx, OtdShowEvents.class);
clickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
clickIntent.putExtra("OTDExtra", extraData);
clickIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendIntent = PendingIntent.getActivity(mCtx, 0, clickIntent,
0);
return pendIntent;
}
If I create an instance of the widget on a clean (rebooted) device, both the widget and notification launch the target activity as expected. However, if I remove the widget and create another instance, the Intent is no longer launched. Likewise, if I uninstall the widget altogether, then re-install it and create an instance, no Intent is fired off. However, if I power off and back on (leaving the widget in place), it works again when booted up.
One error that I saw along the way was from the PackageManager saying "Name not found", but indicating the package name "com.ghcssoftware.OTD.full", which is the correct name of my package!
Any ideas? And by the way, I have tried some of the PendingIntent flags such as FLAG_CANCEL_CURRENT and FLAG_UPDATE_CURRENT without affecting this behavior.
FWIW, I found that the code snippet provided in this article was exactly what I needed to figure out how to get this all working correctly, especially for multiple instances of my widget, etc.: PendingIntent in Widget + TaskKiller
I have an app widget with a Button in it's layout.
When clicking the button, an intent is fired which calls my broadcast receiver.
It works just fine, but occasionally, after using the "Clear memory" button in the Task Manager, the widget gets stuck - clicking on it does nothing. But it can still receive updates from my app, if its running.
I'm not sure if the fact that the pending intent isn't fired is the memory clearing fault, or my fault.
Anyway, here's the code:
Registering the pending intent (onUpdate method of the app widget)
Intent intent = new Intent(context, ServiceControl.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget);
views.setOnClickPendingIntent(R.id.appwidgetbutton, pendingIntent);
and then updating the widgets with the views.
Here is the decleration of the app widget provider:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="72dp"
android:minHeight="72dp"
android:initialLayout="#layout/appwidget"
android:updatePeriodMillis="0">
</appwidget-provider>
I don't want the system to call widget updates, I only update it from my app itself.
So why does the pending intent stop firing?
Thanks in advance.
#Jong
#CommonsWare
Hi guys, I figured it out. Ofc this is an Android issue, the receiver should ALWAYS receive.
Now how to get around it? Obviously all the widgets are working, so there must have been a simple out there.
I read on SO somewhere (trying to find the guy) reminding us all that the widget class is actually extending a BroadcastReceiver.
So, you could register the widget (in the manifest) to receive the threats itself. Thus the entire system is self-contained in the class instance of AppWidgetProvider.
Now, for communicating back with the app, you can in the onReceive call any static class of your app, and LocalBroadcastManager won't fail you if the app is active. If it's not active, your buttons should be starting activities anyway!
Should you want the code, I can detail it.