I developing small application widget and have some problem with updating of remote views. When widget updating through system call all is ok, but when I trying update widget from my service through this code:
AppWidgetManager widgetManager = AppWidgetManager.getInstance(this);
ComponentName widgetComponent = new ComponentName(this, NBRBAppWidget.class);
int[] widgetIds = widgetManager.getAppWidgetIds(widgetComponent);
Intent update = new Intent();
update.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIds);
update.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
sendBroadcast(update);
my widget view glitches. Example of glitch is on the video below
Video of RemoteView glitches
I checked that update views method took 7-20ms
If anybody has some assumption why this happening, please give me to know.
To avoid glitch of widget I just remove sending of ACTION_APPWIDGET_UPDATE broadcast and call RemoteView instead of it, like this:
AppWidgetManager widgetManager = AppWidgetManager.getInstance(this);
ComponentName widgetComponent = new ComponentName(this, NBRBAppWidget.class);
int[] widgetIds = widgetManager.getAppWidgetIds(widgetComponent);
for (int i = 0; i < widgetIds.length; i++) {
NBRBAppWidget.updateWidget(this, widgetManager, widgetIds[i]);
}
NBRBAppWidget.updateWidget is method that do calls to remote views
Related
WHAT I NEED
I'm developing a custom Android launcher for a company, which will be installed on this company's tablets and therefore won't be published on any store.
It's basically a grid with widgets managed remotely (by a background service we're creating), which means that the app should decide which widgets to add or remove whenever the user opens it.
WHAT I HAVE
I'm using the following code to add the widgets:
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getContext());
AppWidgetHost appWidgetHost = new AppWidgetHost(getContext(), APPWIDGET_HOST_ID);
appWidgetHost.startListening();
List<AppWidgetProviderInfo> appWidgetInfos appWidgetInfos = appWidgetManager.getInstalledProviders();
for(int j = 0; j < appWidgetInfos.size(); j++)
{
if (appWidgetInfos.get(j).provider.getPackageName().equals(widgetPackage))
{
// Allocates an id
int appWidgetId = appWidgetHost.allocateAppWidgetId();
// Gets the app widget info
AppWidgetProviderInfo appWidgetProviderInfo = appWidgetInfos.get(j);
// Creates Widget
AppWidgetHostView hostView = appWidgetHost.createView(getContext(), appWidgetId, appWidgetProviderInfo);
hostView.setAppWidget(appWidgetId, appWidgetProviderInfo);
// Insers the view to the layout grid
insertView(row, column, rowspan, columnspan, hostView);
//
break;
}
}
And it works just fine. The widget shows and it's buttons respond to user touch.
Here's one of the the widgets onUpdate:
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
// Perform this loop procedure for each App Widget that belongs to this provider
for (int appWidgetId : appWidgetIds)
{
// Create an Intent to launch ExampleActivity
Intent launchAppIntent = new Intent(context, MapActivity.class);
PendingIntent launchApp = PendingIntent.getActivity(context, 0, launchAppIntent, 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_layout);
views.setOnClickPendingIntent(R.id.widget_layout, launchApp);
// Tell the AppWidgetManager to perform an update on the current app widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
THE PROBLEM
One of the widgets (shown above) calls an Intent to start it's fullscreen Activity when clicked, and this doesn't work on my custom launcher, though it works perfectly on the default home launcher.
I am updating the widget from service(srvice and widget class are in two different package) like this
AppWidgetManager widgetManager = AppWidgetManager.getInstance(this);
ComponentName widgetComponent = new ComponentName(this,MyWidgetProvider.class);
int[] widgetIds = widgetManager.getAppWidgetIds(widgetComponent);
Intent update = new Intent();
update.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIds);
update.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
sendBroadcast(update);
it updates my widget but the problem is that it also updating other widgets of the device too because of which my widget updates after all the widget gets updated.
So now I want to update only my widget how can i do that
According Android documentation getAppWidgetIds return a list of id that have been bound to specific WidgetProvider, that is all your widget instance id not id of widgets under other WidgetProvider.
I have a very weird problem with my AppWidget in Android 4.1.1. I'm currently developing a music player application and its widget. Widget must be updated when song changes, player starts and stops. It has a ListView which must be in sync with playlist in application.
Prior to Jelly Bean everything was working fine. After my test device is upgraded from 4.0.3 to 4.1.1, whenever widget is programmatically forced to update, layout of Android's Messaging widget is set to my widget for a few seconds! I also checked this case in emulator with 4.1, which works fine.
I use that piece of code to force my widget to update:
AppWidgetManager man = AppWidgetManager.getInstance(applicationContext);
int[] ids = man.getAppWidgetIds(
new ComponentName(applicationContext, MuzikLargeWidgetProvider.class));
Intent updateIntent = new Intent();
updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
applicationContext.sendBroadcast(updateIntent);
And here is my onUpdate method
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
this.context = context;
this.appWidgetManager = appWidgetManager;
ComponentName thisWidget = new ComponentName(context,
MuzikLargeWidgetProvider.class);
int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
for (int appWidgetId : allWidgetIds) {
Intent svcIntent = new Intent(context, UpdateService.class);
svcIntent.putExtra(WidgetActions.DATA_WIDGET_ID, appWidgetId);
context.startService(svcIntent);
}
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
I'm using a service (UpdateService), which is a static inner class, to update my widget:
public static class UpdateService extends IntentService {
public UpdateService() {
super("UpdateService");
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
public RemoteViews buildUpdate(Context context, int widgetId) {
final RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.muzikwidget_large);
return views;
}
#Override
protected void onHandleIntent(Intent intent) {
// Build the widget update
RemoteViews updateViews = buildUpdate(this, intent.getIntExtra(WidgetActions.DATA_WIDGET_ID, 0));
// Push update for this widget to the home screen
ComponentName thisWidget = new ComponentName(this, MuzikLargeWidgetProvider.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
manager.updateAppWidget(thisWidget, updateViews);
}
}
buildUpdate method does a few more things (setting intents for widget's buttons, setting textviews, etc.) but they are not related with my problem. I'm having this problem on a Asus TF 300 TG tablet (not rooted).
Any help is appreciated.
The problem is resolved. I modified the code piece which I use to force widget to update. This is working:
AppWidgetManager man = AppWidgetManager.getInstance(applicationContext);
int[] ids = man.getAppWidgetIds(
new ComponentName(applicationContext, MuzikLargeWidgetProvider.class));
for (int appWidgetId : ids) {
Intent svcIntent = new Intent(applicationContext, UpdateService.class);
svcIntent.putExtra(WidgetActions.DATA_WIDGET_ID, appWidgetId);
applicationContext.startService(svcIntent);
}
Widget's onUpdate method is not called and UpdateService is invoked directly. It seems like sendBroadcast should be avoided sometimes.
EDIT
If you need to use broadcast to update your widget, this seems to be a proper way to accomplish that:
AppWidgetManager man = AppWidgetManager.getInstance(applicationContext);
int[] ids = man.getAppWidgetIds(
new ComponentName(applicationContext, MuzikLargeWidgetProvider.class));
Intent updateIntent = new Intent(applicationContext, MuzikLargeWidgetProvider.class);
updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
applicationContext.sendBroadcast(updateIntent);
Edit: I installed my app 4.x devices and there is no problem. Problem exists only with 3.x devices.
I am trying to update an AppWidget manually when a user setting
changes on the device. To do so I use code similar to this:
ComponentName thisWidget = new ComponentName(this, MyAppWidget.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
manager.updateAppWidget(thisWidget, updateViews);
I can tell that my AppWidget onUpdate method is being called as a
result since I log some debug strings to logcat in the code. However
the AppWidget itself doesn't change on the screen.
Here comes the interesting part: if I rotate my device and force a
refresh of the home screen (from PORTRAIT to LANDSCAPE or vice-versa)
then finally my AppWidget gets updated. However rotating the device
does not trigger the onUpdate method to be called, so the AppWidget
must be using the RemoteViews provided in the earlier update.
Can somebody explain me what to do to force the home screen redraw of
my AppWidget when it processes an update?
I am using <uses-sdk android:minSdkVersion="11" />
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
// Get all ids
ComponentName thisWidget = new ComponentName(context, MyProvider.class);
int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
// Build the intent to call the service
Intent intent = new Intent(context.getApplicationContext(), MyService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds);
RemoteViews views = new RemoteViews(context.getPackageName(),R.layout.appwidget_layout);
appWidgetManager.updateAppWidget(appWidgetIds, views);
// Update the widgets via the service
context.startService(intent);
}
And my service:
#Override
public void onStart(Intent intent, int startId) {
Log.i(LOG, "Called");
this.appWidgetManager = AppWidgetManager.getInstance(this.getApplicationContext());
this.allWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
ComponentName thisWidget = new ComponentName(getApplicationContext(), MyProvider.class);
int[] allWidgetIds2 = appWidgetManager.getAppWidgetIds(thisWidget);
Log.w(LOG, "From Intent" + String.valueOf(allWidgetIds.length));
Log.w(LOG, "Direct" + String.valueOf(allWidgetIds2.length));
config = getResources().getConfiguration();
for (int widgetId : allWidgetIds) {
//do things like starting ASYNCs (couple of them) to fetch data from server
//and set an adapter for the gridview
}
Intent intentOpen = new Intent(getApplicationContext(), MainFragmentActivity.class);
PendingIntent open = PendingIntent.getActivity(getApplicationContext(), 0, intentOpen, 0);
remoteViews.setOnClickPendingIntent(R.id.widget_whole, open);
remoteViews.setViewVisibility(R.id.widgetProgress, View.GONE);
appWidgetManager.updateAppWidget( thisWidget, remoteViews );
Log.d(LOG, "sent!!");
}
stopSelf();
super.onStart(intent, startId); }
This sounds like a bug in the launcher app to me. Every time you call updateAppWidget the AppWidgetManager receives the new layout and the launcher is responsible for redrawing the widget with this new layout.
The fact that when the screen is rotated onUpdate and updateAppWidget are not called, but the launcher redraws the widget with its new layout means that the AppWidgetManager has already successfully received that new layout.
When the screen is rotated the launcher is forced to redraw everything (its Activity is recreated) and that is why it shows the new layout.
And this bug is probably fixed in Android 4.0+
I would try calling invalidate() on the Views in your widget. Calling that forces the system to redraw the View.
I read some example in internet/book about the App Widget, a normal example to update the widget is in onUpdate(Context context, AppWidgetManager appWidgetManager,int[] appWidgetIds) method of AppWidgetProvider like this:
final int N = appWidgetIds.length;
for (int i=0; i<N; i++) {
int appWidgetId = appWidgetIds[i];
RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.mywidget_layout);
updateViews.setTextViewText(R.id.mytext, "updated text");
appWidgetManager.updateAppWidget(appWidgetId, updateViews);
}
It update each Widgets in a loop.
But now, I have to implement an App Widget, it is updated in BroadcastReceiver, onReceive(Context context, Intent intent) method since there are no int[] appWidgetIds passed in. So I implemented the code like this:
RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.mywidget_layout);
updateViews.setTextViewText(R.id.mytext, "updated text");
ComponentName myComponentName = new ComponentName(context, AndroidBatteryWidgetProvider.class);
AppWidgetManager manager = AppWidgetManager.getInstance(context);
manager.updateAppWidget(myComponentName, updateViews);
It didn't update widget one-by-one, but actually all widgets were updated at once. Even though it worked as I want, but I got confused as to why there is no need to update all widgets one-by-one as before.
What's the difference between two methods?
Can I send another broadcast from BroadcastReceiver.onReceive() to trigger AppWidgetProvider.onUpdate()? And how to?
It's the same thing. Update with ComponentName loops through all the ids like your first code block.
You can see it in the Android code here:
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.4_r1.2/com/android/server/AppWidgetService.java#AppWidgetService.updateAppWidgetProvider%28android.content.ComponentName%2Candroid.widget.RemoteViews%29