How to update App Widget with list view from an Ac

2019-04-04 05:17发布

问题:

I know this has been asked for many times but I went through the documentation from top to bottom, read all answers here and none of them helped. To be honest, each answer says something different about how to aproach this.

Now back to my question. I want to update the widget list view from some activity and I created WidgetProvider#sendUpdateBroadcastToAllWidgets() for this purpose which I call from the activity.

It eventually calls the onUpdate() so the broadcast is received correctly. But the views are not refreshed.

I also tried to call AppWidgetManager#notifyAppWidgetViewDataChanged() and refreshed the data in WidgetFactory#onDataSetChanged() but the method has never been called.

So I guess this all does not work because the remote views factory is cached but I don't know how to reliably overcome this. Any thoughts?

And what about contexts? I always have to supply one but I don't really care much which one. Does it matter?

Thanks

PROVIDER

public class WidgetProvider extends AppWidgetProvider {

    public static void sendUpdateBroadcastToAllWidgets(Context context) {
        int allWidgetIds[] = AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, WidgetProvider.class));
        Intent intent = new Intent(context, WidgetProvider.class);
        intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds);
        context.sendBroadcast(intent);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager widgetManager, int[] widgetIds) {
        for (int id : widgetIds) {
            updateWidget(context, widgetManager, id);
        }
        super.onUpdate(context, widgetManager, widgetIds);
    }

    @Override
    public void onDeleted(Context context, int[] widgetIds) {
        WidgetPreferences prefs = new WidgetPreferences(context);
        for (int widgetId : widgetIds) {
            prefs.getWidgetPreferences(widgetId).edit().clear().commit();
        }
        super.onDeleted(context, widgetIds);
    }

    private static void updateWidget(Context context, AppWidgetManager widgetManager, int widgetId) {
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);

        // set list adapter
        Intent serviceIntent = new Intent(context, WidgetService.class);
        serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
        serviceIntent.setData(Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME)));
        views.setRemoteAdapter(android.R.id.list, serviceIntent);
        views.setEmptyView(android.R.id.list, android.R.id.empty);

        // set widget title
        WidgetDataCategory category = new WidgetPreferences(context).getSavedCategory(widgetId);
        views.setTextViewText(R.id.titleText, context.getString(category.titleResourceId()));

        // set onclick listener - we create a pending intent template and when an items is clicked
        // the intent is filled with missing data and sent
        Intent startActivityIntent = new Intent(context, SimplePersonDetailActivity.class);
        startActivityIntent.setData(Uri.parse(startActivityIntent.toUri(Intent.URI_INTENT_SCHEME)));
        startActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        startActivityIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, startActivityIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        views.setPendingIntentTemplate(android.R.id.list, pendingIntent);

        // all hail to Google
        widgetManager.updateAppWidget(widgetId, views);
    }
}

FACTORY

public class WidgetFactory implements RemoteViewsService.RemoteViewsFactory {

    private Context context;
    private List<Person> people = new ArrayList<>();

    public WidgetFactory(Context context, Intent intent) {
        this.context = context;

        int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
        if (widgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
            WidgetPreferences prefs = new WidgetPreferences(context);

            WidgetDataCategory category = prefs.getSavedCategory(widgetId);
            int numberOfItemsToShow = prefs.getSavedLimit(widgetId);

            people = category.filterAndSlice(new PersonDao(context).getAllForGroup(Constants.SIMPLE_GROUP_ID), numberOfItemsToShow);
        }
    }

    @Override
    public void onCreate() {}

    @Override
    public void onDataSetChanged() {}

    @Override
    public void onDestroy() {}

    @Override
    public int getCount() {
        return people.size();
    }

    @Override
    public RemoteViews getViewAt(int position) {
        Person person = people.get(position);
        BigDecimal amount = ListViewUtil.sumTransactions(new TransactionDao(context).getAllForPerson(person));

        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.item_widget);
        remoteViews.setTextViewText(R.id.nameText, person.getName());
        remoteViews.setTextViewText(R.id.amountText, MoneyFormatter.withoutPlusPrefix().format(amount));

        // fill details for the onclick listener (updating the pending intent template
        // set in the WidgetProvider)
        Intent listenerIntent = new Intent();
        listenerIntent.putExtra(Constants.PERSON_ID, people.get(position).getId());
        remoteViews.setOnClickFillInIntent(R.id.widgetItem, listenerIntent);

        return remoteViews;
    }

    @Override
    public RemoteViews getLoadingView() {
        return null;
    }

    @Override
    public int getViewTypeCount() {
        return 1;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public boolean hasStableIds() {
        return true;
    }
}

回答1:

I would say that notifyAppWidgetViewDataChanged method should work for you.

You have to build AppWidgetManager and get appWidgetIds and then just call notifyAppWidgetViewDataChanged on your AppWidgetManager.

Pseudo Code,

AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int appWidgetIds[] = appWidgetManager.getAppWidgetIds(
                           new ComponentName(context, WidgetProvider.class));
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.listview);

For more, you can checkout my answer here which contains demo on github.



回答2:

I had a widget implementation in my project. I have modified the below code so that data in widget can be changed from one of my Activity in application. Only showing the essential code for your specific use case.

Here I am having a button with text in my widget. Through login button click in my Activity , I am modifying the button text in my widget

Below is my AppWidgetProvider.java class

public class AppWidgetTrackAsset extends AppWidgetProvider{
      @Override
      public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // Perform this loop procedure for each App Widget that belongs to this provider
        final int N = appWidgetIds.length;

        for (int i=0; i<N; i++) {
            int appWidgetId = appWidgetIds[i];
            // Create an Intent to launch Activity
            Intent intent = new Intent(context, WidgetAlertActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 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.app_widget_track_asset);
            views.setOnClickPendingIntent(R.id.sosButton, pendingIntent);
            // Tell the AppWidgetManager to perform an update on the current app widget
            appWidgetManager.updateAppWidget(appWidgetId, views);
        }
    }
    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        Log.v(Constants.WIDGET_LOG, "onReceive called with " + intent.getAction());
        if (intent.getAction().equals("update_widget")) {
            // widget update started
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
                    R.layout.app_widget_track_asset);
            // Update text , images etc
            remoteViews.setTextViewText(R.id.sosButton, "My updated text");
            // Trigger widget layout update
            AppWidgetManager.getInstance(context).updateAppWidget(
                    new ComponentName(context, AppWidgetTrackAsset.class), remoteViews);
        }
    }
    @Override
    public void onEnabled(Context context) {
        // Enter relevant functionality for when the first widget is created
    }

    @Override
    public void onDisabled(Context context) {
        // Enter relevant functionality for when the last widget is disabled
    }
}


Below is my Activity where I am updating the widget button text on click of my login button
LoginActivity.java

public class LoginActivity extends AppCompatActivity implements View.OnClickListener{

  Button loginButton;
  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        loginButton=(Button)findViewById(R.id.loginButton);
        loginButton.setOnClickListener(this);

    }
    @Override
    public void onClick(View v) {
        if(v.getId() == R.id.loginButton){
            updateWidget();
        }
    }
    private void updateWidget(){
        try {
            Intent updateWidget = new Intent(this, AppWidgetTrackAsset.class);
            updateWidget.setAction("update_widget");
            PendingIntent pending = PendingIntent.getBroadcast(this, 0, updateWidget, PendingIntent.FLAG_CANCEL_CURRENT);
            pending.send();
        } catch (PendingIntent.CanceledException e) {
            Log.e(Constants.UI_LOG,"Error widgetTrial()="+e.toString());
        }
    }
}


Layout for app widget goes like this

app_widget_track_asset.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/widgetbackground"
    android:padding="@dimen/widget_margin">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/sosButton"
        android:text="SOS"
        android:textSize="20sp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        />
</RelativeLayout>

Below is the manifest file essential part for widget

<receiver android:name=".appwidget.AppWidgetTrackAsset">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/app_widget_track_asset_info" />
        </receiver>