I'm creating a test widget that shows random number by clicking its button. everything is inside onUpdate
of my Provider
independently, including the pendingIntent
. it works fine but after rebooting the phone views.setOnClickPendingIntent
is not working although RemoteViews
is recreated with no issue but the button becomes unresponsive.
public class TestWidget extends AppWidgetProvider {
static HashMap<Integer, BroadcastReceiver> br = new HashMap<>();
static void updateAppWidget(Context context, final AppWidgetManager appWidgetManager,
final int appWidgetId) {
context = context.getApplicationContext();
final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.test_widget);
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
views.setTextViewText(R.id.appwidget_text, Math.random() + "");
appWidgetManager.updateAppWidget(appWidgetId, views);
}
};
br.put(appWidgetId, broadcastReceiver);//to unregister later
Intent intent = new Intent("action");
IntentFilter intentFilter = new IntentFilter("action");
context.registerReceiver(broadcastReceiver, intentFilter);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 123, intent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.appwidget_button, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
context.unregisterReceiver(br.get(appWidgetId));
}
}
}
manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.aeza.sta">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<receiver android:name=".TestWidget" android:enabled="true" android:exported="false" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/test_widget_info" />
</receiver>
<activity android:name=".TestWidgetConfigureActivity">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
</application>
</manifest>
Dynamically registering BroadcastReceiver
s in an AppWidgetProvider
is a shaky solution, at best. AppWidgetProvider
itself is a BroadcastReceiver
, and instances of those statically registered in an app's manifest are meant to be rather short-lived.
However, since AppWidgetProvider
is a BroadcastReceiver
, we can take advantage of that, and simply target your TestWidget
in the click PendingIntent
s. We can also attach the Widget ID as an extra to the Intent
here, so we update the correct one when the click fires. For example:
Intent intent = new Intent(context, TestWidget.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
appWidgetId,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.appwidget_button, pendingIntent);
Note that we've also used the appWidgetId
for the PendingIntent
's requestCode
. It's important that a distinct PendingIntent
is used for each Widget instance, lest the wrong Widget instance be updated with the wrong extras. Using the already-unique Widget ID allows us to do that easily.
We then override TestWidget
's onReceive()
method, and check the Intent
's action to determine if this is our click broadcast, or a normal Widget event broadcast from the system. In the example above, we didn't set an action, so we'll simply check for null here. However, you certainly could specify an action String
, and it may be preferable to do so in some cases; e.g., if you have multiple Button
s in your Widget, and need to distinguish their click broadcasts.
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction() == null) {
int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
if (appWidgetId != -1) {
updateWidgetText(context, appWidgetId, Math.random() + "");
}
}
else {
super.onReceive(context, intent);
}
}
In the above, you can see that we pass the broadcast to the super
method if we've found that it's not ours. AppWidgetProvider
's onReceive()
will then examine the Intent
, and delegate to the appropriate event method, per usual.
Apart from being a stable solution, this approach has another upshot, in that a separate BroadcastReceiver
instance does not need to be created, registered, and then unregistered for each Widget instance. Though we've added an onReceive()
method, we can remove all of the dynamic BroadcastReceiver
code, so our TestWidget
class is still pretty short and simple.
public class TestWidget extends AppWidgetProvider {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction() == null) {
int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
if (appWidgetId != -1) {
updateWidgetText(context, appWidgetId, Math.random() + "");
}
}
else {
super.onReceive(context, intent);
}
}
static void updateWidgetText(Context context, int appWidgetId, String newText) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.test_widget);
views.setTextViewText(R.id.appwidget_text, newText);
AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, views);
}
static void updateAppWidget(Context context, final AppWidgetManager appWidgetManager,
final int appWidgetId) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.test_widget);
Intent intent = new Intent(context, TestWidget.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
appWidgetId,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.appwidget_button, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
}