ListView widget with multiple pending intents
Asked Answered
H

1

8

I'm using widget with listview that has 3 types of items. For each type of item I should use different pending intents. Currently I'm using following code:

public class MyWidgetProvider extends AppWidgetProvider {

    @Override
    public void onUpdate(Context context, AppWidgetManager widgetManager, int[] widgetIds) {
        for (int widgetId : widgetIds) {
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_view);
            bindEmptyView(remoteViews);
            bindRemoteAdapter(context, widgetId, remoteViews);
            bindIntentTemplate(context, widgetId, remoteViews);
            widgetManager.updateAppWidget(widgetId, remoteViews);
        }
    }

    private void bindEmptyView(RemoteViews remoteViews) {
        remoteViews.setEmptyView(android.R.id.list, android.R.id.empty);
    }

    private void bindRemoteAdapter(Context context, int widgetId, RemoteViews remoteViews) {
        Intent intent = new Intent(context, MyViewService.class);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
        intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
        remoteViews.setRemoteAdapter(android.R.id.list, intent);
    }

    private void bindIntentTemplate(Context context, int widgetId, RemoteViews remoteViews) {

        Intent intent = new Intent(context, MyActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
        intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
        PendingIntent template = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        remoteViews.setPendingIntentTemplate(android.R.id.list, template);
    }
}

From this point I don't understand how can I invoke specific intent for different list items.

Heartache answered 22/8, 2014 at 6:57 Comment(0)
V
12

Hopefully I understood your issue well, so I'll try to explain in detail what's happening and how to handle clicks on list items on the widget.

I assume you already know that you have to implement a class which:

extends BroadcastReceiver implements RemoteViewsService.RemoteViewsFactory

This will serve as the "adapter" for your widget's ListView(let's call it MyListRemoteViewFactory). If you want to handle item clicks on a widget's listView, you do the following things:

1) set a setPendingIntentTemplate in your AppWidgetProvider class

2) set a setOnClickFillInIntent in the MyListRemoteViewFactory overridden getViewAt(int position) method

NOW: Doing step 1), you might wanna do something like:

final Intent serviceIntent = new Intent(context, MyListRemoteViewFactory.class);
serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
serviceIntent.setData(Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME)));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
    views.setRemoteAdapter(R.id.widget_list_view, serviceIntent);
} else {
    views.setRemoteAdapter(widgetId, R.id.widget_list_view, serviceIntent);
}

// Individuals items of a collection cannot set up their own pending intents. Instead, the collection as a whole sets up a pending intent template and the individual
// items set a fillInIntent to create unique behavior on an item-by-item basis.
Intent listItemClickIntent = new Intent(context, MyWidgetProvider.class); // This is the name of your AppWidgetProvider class
// Set the action for the intent. When the user touches a particular view, it will have the effect of broadcasting an action
listItemClickIntent.setAction(context.getString("com.example.list.item.click"));
 listItemClickIntent.setData(Uri.parse(listItemClickIntent.toUri(Intent.URI_INTENT_SCHEME)));
PendingIntent clickPendingIntent = PendingIntent.getBroadcast(context, 0, listItemClickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setPendingIntentTemplate(R.id.widget_list_view, clickPendingIntent);

You can place the above code snippet after wherever you initialize your RemoteViews object:

RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.layout_widget);)

So right now you have your pendingIntentTemplate ready. One other thing to do is to implement the class' onReceive method, so you can decide what to do when an action for the above case occurred. So you'll do something like:

@Override
public void onReceive(Context context, Intent intent) {
    // Called on every broadcast and before each of the above callback methods.
    super.onReceive(context, intent);

    ComponentName name = new ComponentName(context, WidgetProvider.class);
    int[] appWidgetIds = AppWidgetManager.getInstance(context).getAppWidgetIds(name);
    if (appWidgetIds == null || appWidgetIds.length == 0) {
        return;
    }

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

    if (intent.getAction().equals("com.example.list.item.click") { 
        // This is where you have to decide what you'll do depending on which list item was tapped. BUT this depends on the fill Intent set in the MyListRemoteViewFactory class's getViewAt() method
        // I'll jump straight to the logic here, but once you're done reading this post, get back here to understand the logic because this is the key here. But simple as hell.
        int simpleDecidingFactor =  intent.getIntExtra("SELECTED_ITEM", 0)
        if (simpleDecidingFactor != 0) {
            if (simpleDecidingFactor == 1) {
                // Do something for the first case. Probably open activity2
            } else if (simpleDecidingFactor == 2) {
                // Do something for the second case. Probably open activity2
            } else if (simpleDecidingFactor == 3) {
                // Do something for the second case. Probably open activity3
            }
         }
    }
}

[Damn this'll be long]

DOING STEP 2) (from now on we talk about implementations in the MyListRemoteViewFactory class)

If you need 3 different items in the list, first you have to add this method to the MyListRemoteViewFactory(you're forced to override it anyway, the key is to return the number of views you have):

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

In the getViewAt() method you add your logic based on what, you decide what to display depending on your position. Something like:

@Override
public RemoteViews getViewAt(int position) {
    if (position >= mItems.size()) {
        return null;
    }

    RemoteViews views;
    if (mItems.get(position).getViewType() == 0) {
        views = new RemoteViews(mContext.getPackageName(), R.layout.list_item_first);
        setUpItem(views, mItems.get(position), 1); // !!!! the 3rd parameter is very important here, as you'll expect this number in the MyWidgetProvider class' onReceive method. See the simpleDecidingFactor variable there.
    } else if (mItems.get(position).getViewType() == 1) {
        views = new RemoteViews(mContext.getPackageName(), R.layout.list_item_second);
        setUpItem(views, mItems.get(position), 2);
    } else {
        views = new RemoteViews(mContext.getPackageName(), R.layout.list_item_third);
        setUpItem(views, mItems.get(position), 3);
    } // Or add whatever logic you have. Here, I supposed I have a field inside my object telling me what type my item is

    return views;
}

And the setUpItem method might look something like:

private void setUpItem(RemoteViews views, MyObject object, int viewTypeKey) {
     // This is where you set your clickFillInIntent. Without setting it, nothing'll be functional

    Bundle extras = new Bundle();
    extras.putInt("SELECTED_ITEM", viewTypeKey);
    //extras.putParcelable("LIST_ITEM_OBJECT", object); // You may send your object as well if you need it
    Intent fillInIntent = new Intent();
    fillInIntent.putExtras(extras);

    // You have your fillInIntent prepared, you only have to decide on what view to place it.
    // I assume you have a Button on all 3 of your list item layouts with the id button_click. Let's place the Intent:

    views.setOnClickFillInIntent(R.id.button_click, fillInIntent);
}

You might want to make sure you declared everything in the Manifest file as well. You have to declare your widgetprovider, your receiver for the list, and a service handling your Factoryclass. You should have something like:

<receiver
    android:name=".MyWidgetProvider"
    android:enabled="true"
    android:label="My awesome widget">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_ENABLED" />
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        <action android:name="android.appwidget.action.APPWIDGET_DELETED" />
        <action android:name="android.appwidget.action.APPWIDGET_DISABLED" />
        <!-- You have to declare your used actions here, so the AppWidgetProvider knows what to listen for-->
            <action android:name="com.example.list.item.click"/>
    </intent-filter>

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

<!-- The service serving the RemoteViews to the collection widget -->
<service
    android:name=".WidgetRemoteViewsService"
    android:exported="false"
    android:permission="android.permission.BIND_REMOTEVIEWS"/>

<receiver
    android:name=".ui.widget.MyListRemoteViewFactory"
    android:enabled="true"
    android:exported="false">
    <intent-filter>
        <category android:name="android.intent.category.DEFAULT"/>
        <!-- You might want to use an action to notify the appwidget provider from the MyListRemoteViewFactory's onReceive method. This class extends a BroadcastReceiver, so you must implement it's onReceive(Context context, Intent intent) method. If you need help on this, let me know and I'll edit my answer with some example for that too -->
        <action android:name="com.example.refresh.remote.views"/>
    </intent-filter>
</receiver>

Ah, BTW the WidgetRemoteViewsService class should look like this:

public class WidgetNewsRemoteViewsService extends RemoteViewsService {

    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new MyListRemoteViewFactory();
    }
}

I guess this is pretty much it. I hope I didn't skip anything.

Validate answered 12/9, 2014 at 15:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.