4

I'm developing an Android Widget for an app, and the problem is I can't set onClickPendingIntent() on a button in a RemoteViewsFactory.

I explain: I created an AppWidgetProvider, which calls an extending of RemoteViewsService which calls an extending of RemoteViewsFactory for complete a ListView in my widget. The RemoteViewsFactory have to return all items for update or create them and display on the widget. But for each items of the list view, I have 2 types of buttons:

  1. A button which opens gmaps/dialer/sms (It works).
  2. A button which calls an activity in my app and send it in a parameter the ID of the item.

And the problem is the second button, my solution doesn't work.

Here is the problem:

And so, this is the code which doesn't work:

row.setOnClickPendingIntent(R.id.taskButton, onClickPendingIntent);

// Creating an onclick event for the done button
Intent doneIntent = new Intent(mContext, WidgetProvider.class);
doneIntent.putExtra("DONE_TASK", "DOOOOM");

PendingIntent onDoneIntent = PendingIntent.getActivity(mContext, 0, doneIntent, 0);
row.setOnClickPendingIntent(R.id.doneButton, onDoneIntent);

Here is the complete WidgetFactory class:

import java.util.Collections;
import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.Vector;

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.view.View;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;

public class WidgetFactory implements RemoteViewsService.RemoteViewsFactory {

    private Vector<EventInfo> mAllEvents;
    private Context mContext;

    public WidgetFactory(Context ctxt, Intent intent) {
        // Creating member vars
        mContext = ctxt;

        updateAllEventsVector();
    }


    private void updateAllEventsVector() {
        SharedInstances sharedInstances = SharedInstances.get();
        mAllEvents = new Vector<EventInfo>(); 

        if (sharedInstances != null) {
            TaskRequestManager taskManager = sharedInstances
                    .getTaskRequestManager();
            CalendarRequestManager calManager = sharedInstances
                    .getCalendarRequestManager();

            Vector<TaskEvent> tasks = null;
            Vector<CalendarEvent> events = null;

            if (taskManager != null) 
                tasks = taskManager.readTasksToday(mContext);

            if (calManager != null)
                events = calManager.readCalendarEventsToday(mContext);

            if (!tasks.isEmpty())
                mAllEvents.addAll(tasks);


            if (!events.isEmpty())
                mAllEvents.addAll(events);

            mAllEvents = sortByDate(mAllEvents);
        }
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public Vector<EventInfo> sortByDate(Vector<EventInfo> events)
    {

        Vector<EventInfo> sortedEvents = new Vector<EventInfo>();

        for(EventInfo event : events)
        {
            if ((event.getStartTime()+event.getEventDuration()) > GregorianCalendar.getInstance().getTimeInMillis())
                sortedEvents.add(event);
        }

        Collections.sort(events, new Comparator() {
            public int compare(Object arg0, Object arg1)
            {
                EventInfo event0 = (EventInfo)arg0;
                EventInfo event1 = (EventInfo)arg1;

                if (event0.getStartTime()+event0.getEventDuration() > event1.getStartTime()+event1.getEventDuration())
                    return 1;
                else if (event0.getStartTime()+event0.getEventDuration() == event1.getStartTime()+event1.getEventDuration())
                    return 0;
                else if (event0.getStartTime()+event0.getEventDuration() < event1.getStartTime()+event1.getEventDuration())
                    return -1;

                return 0;
            }
        });

        return sortedEvents;
    }

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

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

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

    @Override
    public RemoteViews getViewAt(int position) {

        // Getting item view
        RemoteViews row = new RemoteViews(mContext.getPackageName(),
                R.layout.done_task_item);

        EventInfo eventInfo = mAllEvents.get(position);



        row.setInt(R.id.item_event, "setBackgroundColor", Color.argb(60, Color.red(eventInfo.getColor()), Color.green(eventInfo.getColor()), Color.blue(eventInfo.getColor())));

        // Converts startTime and endTime in string
        String startTime = TimeCursor.getAdaptativeTime(eventInfo.getStartTime());
        String endTime = TimeCursor.getAdaptativeTime((eventInfo
                .getEventDuration() + eventInfo.getStartTime()));

        //Get title
        String title = eventInfo.getTitle();

        // Setting data in the view
        row.setTextViewText(R.id.titleTask, title);
        row.setTextViewText(R.id.periodTask, startTime + " to " + endTime);

        //Check type of event
        if (eventInfo.isTask()) {
            //endDate > GregorianCalendar.getInstance().getTimeInMillis() ) {

            //Check if action exists
            if (eventInfo.getAction() != null) {

                //Get the action title
                String action = eventInfo.getAction()
                        .getTitleText();

                //Create a onClickPendingIntent for taskButton
                PendingIntent onClickPendingIntent = null;

                //Add related button
                if (action.equals("Call"))
                {
                    //Set call icon to taskButton
                    row.setImageViewResource(R.id.taskButton, R.drawable.ic_call_white );

                    //Get numbers from the contact
                    Vector<TelOrEmailItem> tel = eventInfo.getContact().getAllPhoneNumbers(mContext.getResources() , mContext, eventInfo.getAction());

                    // Creating an onclick event for call somebody
                    Intent callIntent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:"+tel.get(0).getMainText()));
                    onClickPendingIntent = PendingIntent.getActivity(
                            mContext, 0, callIntent, 0);

                }
                else if (action.equals("SMS"))
                {
                    //Set sms icon to taskButton
                    row.setImageViewResource(R.id.taskButton, R.drawable.ic_sms_white);
                    //Get numbers from the contact
                    Vector<TelOrEmailItem> tel = eventInfo.getContact().getAllPhoneNumbers(mContext.getResources() , mContext, eventInfo.getAction());

                    // Creating an onclick event for call somebody
                    Intent smsIntent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:"+tel.get(0).getMainText()));
                    onClickPendingIntent = PendingIntent.getActivity(
                            mContext, 0, smsIntent, 0);
                }
                /*else if (action.equals("Chat with"))
                    row.setImageViewResource(R.id.taskButton, R.drawable.ic_chat_white);*/
                else if (action.equals("eMail") || action.equals("Mail") || action.equals("Write to"))
                {
                    //Set email icon to taskButton
                    row.setImageViewResource(R.id.taskButton, R.drawable.ic_email_white);

                    //Get numbers from the contact
                    Vector<TelOrEmailItem> tel = eventInfo.getContact().getAllEMails(mContext, eventInfo.getAction());

                    //Creating an onclick event for email somebody
                    Intent emailIntent = new Intent(Intent.ACTION_SEND);
                    emailIntent.setType("plain/text");

                    emailIntent.putExtra(Intent.EXTRA_EMAIL, 
                                    new String[]{tel.get(0).getMainText()}); 

                    onClickPendingIntent = PendingIntent.getActivity(
                            mContext, 0, emailIntent, 0);
                }
                /*else if (action.equals("Skype"))
                    row.setImageViewResource(R.id.taskButton, R.drawable.ic_skype_white);*/

                //Assign the intent to the taskButton
                row.setOnClickPendingIntent(R.id.taskButton, onClickPendingIntent);

                // Creating an onclick event for the done button
                Intent doneIntent = new Intent(mContext, WidgetProvider.class);

                doneIntent.putExtra("DONE_TASK", "DOOOOM");

                PendingIntent onDoneIntent = PendingIntent.getActivity(mContext, 0, doneIntent, 0);

                row.setOnClickPendingIntent(R.id.doneButton, onDoneIntent);

            }
            else
                row.setViewVisibility(R.id.taskButton, View.GONE); //hidde the taskButton

            return row;

        }
        //Check if it's an event
        else if(eventInfo.isEvent()) {

            //hidde task button (Done)
            row.setViewVisibility(R.id.doneButton, View.GONE);

            CalendarEvent ev = eventInfo.getCalendarEvent();
            String location = ev.getEventLocation();
            if (location != null && !location.isEmpty())
            {
                //Set the locate icon on the taskButton
                row.setImageViewResource(R.id.taskButton, R.drawable.ic_locate_white);

                //Define the place to open the map
                Intent mapIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("geo:0,0?q="+location));
                PendingIntent onMapIntent = PendingIntent.getActivity(
                        mContext, 0, mapIntent, 0);
                row.setOnClickPendingIntent(R.id.taskButton, onMapIntent);
            }
            else
                row.setViewVisibility(R.id.taskButton, View.GONE);

            return row;
        }


        return null;
    }

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

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

    @Override
    public void onCreate() {
    }

    @Override
    public void onDataSetChanged() {
        // On data changes, update mTasks
        updateAllEventsVector();
    }

    @Override
    public void onDestroy() {
        mAllEvents = null;
    }

}

And thank you :)


Edit: Yeaye ! Problem solved or not...

Method onClickPendingIntent() is working now, here is the code :

// Creating an onclick event for the done button
Intent onClickDone = new Intent(mContext, DoneTaskActivity.class);
onClickDone.putExtra("TASK_ID", eventInfo.getTaskEvent().getTaskId());
PendingIntent onClickPendingDone = PendingIntent.getActivity(mContext, 0, onClickDone, 0);
row.setOnClickPendingIntent(R.id.doneButton, onClickPendingDone);

But another problem exists: The DoneTaskActivity doesn't receive the extra declared as TASK_ID. In the onCreate() method of the DoneTaskActivity, the Bundle var in parameter stays to null.

Help :(

Steve Robbins
  • 13,672
  • 12
  • 76
  • 124
Kyu_
  • 800
  • 4
  • 10
  • 18

3 Answers3

1

When using a list view in a widget you need to use the setOnClickFillInIntent listener. From the official docs;

When using collections (eg. ListView, StackView etc.) in widgets, it is very costly to set PendingIntents on the individual items, and is hence not permitted. Instead a single PendingIntent template can be set on the collection

Inside your RemoteViewsFactory you use it like this;

public RemoteViews getViewAt(int position) {
    // position will always range from 0 to getCount() - 1.

    // Construct a RemoteViews item based on the app widget item XML file, and set the
    // text based on the position.
    RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_item);
    rv.setTextViewText(R.id.widget_item, mWidgetItems.get(position).text);

    // Next, set a fill-intent, which will be used to fill in the pending intent template
    // that is set on the collection view in StackWidgetProvider.
    Bundle extras = new Bundle();
    extras.putInt(StackWidgetProvider.EXTRA_ITEM, position);
    Intent fillInIntent = new Intent();
    fillInIntent.putExtras(extras);
    // Make it possible to distinguish the individual on-click
    // action of a given item
    rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);

    ...

    // Return the RemoteViews object.
    return rv;
}

You also need to use a standard PendingIntent as individual list items can't set PendingIntents, see docs on AppWidgets.

rogermushroom
  • 5,486
  • 4
  • 42
  • 68
0

The first parameter of 'putExtra' must include a package prefix. If your app's package name is 'com.test', you should put 'com.test.TASK_ID'. Same name on receiving part.

juniano
  • 354
  • 4
  • 13
0

try to add this:

// When intents are compared, the extras are ignored, so we need to embed the extras
// into the data so that the extras will not be ignored.
onClickDone.setData(Uri.parse(onClickDone.toUri(Intent.URI_INTENT_SCHEME)));

before

row.setOnClickPendingIntent(R.id.doneButton, onClickPendingDone);
SchwarzeHuhn
  • 638
  • 5
  • 17