Question

So I am trying to use the Picasso Library for image downloading and caching. In order to get the contactUri to pass to Picasso I need to make a query to the Contacts Content Provider. Since I don't want to block the main UI thread to get the contactId, I have put this in an AsyncTask. And once I get that contactId, I make the call to Picasso in the onPostExecute() method of the AsyncTask.

However, I am noticing a flickering that shows up when I scroll through my ListView quickly. It seems to me that there is an issue with the ViewHolder since the recycled views are displaying the previous image before setting the appropriate image. Is there anyway to avoid this?

public class ConversationThreadsCursorAdapter extends SimpleCursorAdapter {

    // region Constants
    private static final int RECIPIENT_IDS_COLUMN_INDEX = 3;
    private static final int ID2_COLUMN_INDEX = 0;
    private static final int ADDRESS_COLUMN_INDEX = 1;
    // endregion

    // region Variables
    private final String DEBUG_TAG = getClass().getSimpleName().toString();

    private Context mContext;
    protected Drawable mDefaultPicDrawable;
    protected ContentResolver mContentResolver;
    protected LinearLayout.LayoutParams mContactPicLayoutParams;
    // endregion

    // region Constructors
    public ConversationThreadsCursorAdapter(Context context, int layout,
    Cursor c, String[] from, int[] to, int flags) {
        super(context, layout, c, from, to, flags);
        mContext = context;
        mDefaultPicDrawable = mContext.getResources().getDrawable(
        R.drawable.ic_contact_picture);
        mContactPicLayoutParams = new LinearLayout.LayoutParams(
        mDefaultPicDrawable.getIntrinsicWidth(),
        mDefaultPicDrawable.getIntrinsicHeight());

    }
    // endregion

    public View getView(final int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder = null;

        if (convertView == null) {
            convertView = mLayoutInflater.inflate(R.layout.simple_message, null);

            // Creates a ViewHolder and store references to the children
            // views we want to bind data to.
            viewHolder = setupViewHolder(convertView);

            convertView.setTag(viewHolder);

        } else {
            // Get the ViewHolder back to get fast access to the TextView
            // and the ImageView.
            viewHolder = (ViewHolder) convertView.getTag();
            viewHolder.task.cancel(true);


        }

        mCursor = getCursor();

        mCursor.moveToPosition(position);

        viewHolder.position = position;

        String recipient_ids = mCursor.getString(RECIPIENT_IDS_COLUMN_INDEX);

        String[] recipients = recipient_ids.split(" ");

        viewHolder.task = new AddressFetcherTask(viewHolder, position);
        viewHolder.task.execute(recipients); 

        return convertView;

    }

    // region Helper Methods
    private ViewHolder bindUIElements(View convertView) {
        ViewHolder viewHolder = new ViewHolder();

        viewHolder.contactBadge = (QuickContactBadge) convertView.findViewById(R.id.contact_pic);
        return viewHolder;
    }

    private ViewHolder setupViewHolder(View convertView) {
        ViewHolder viewHolder = bindUIElements(convertView);

        viewHolder.contactBadge.setLayoutParams(mContactPicLayoutParams);
        return viewHolder;
    }
    // endregion

    // region Inner Classes

    private class ViewHolder {
        QuickContactBadge contactBadge;
        int position;
    }

    private class AddressFetcherTask extends AsyncTask < String[], Void, Integer > {
        private ViewHolder mViewHolder;
        private int mPosition;

        public AddressFetcherTask(ViewHolder viewHolder, int position) {
            mViewHolder = viewHolder;
            mPosition = position;
        }

        @Override
        protected Integer doInBackground(String[]...recipients) {
            String recipient = recipients[0][0];
            Log.d(DEBUG_TAG, "recipient is " + recipient);
            Cursor c = mContentResolver.query(
            Uri.parse("content://mms-sms/canonical-addresses"), null, "_id = " + recipient, null, null);

            String _id = "";
            String address = "";
            while (c.moveToNext()) {
                _id = c.getString(ID2_COLUMN_INDEX);
                address = c.getString(ADDRESS_COLUMN_INDEX);
            }
            c.close();

            int contactId;
            if (address != null) {
                contactId = ContactsUtils.getContactId(mContext, address, "address");
            } else {
                contactId = Integer.valueOf(address);
            }
            return contactId;
        }

        @Override
        protected void onPostExecute(Integer contactId) {

            if (mViewHolder.position == mPosition) {
                Picasso.with(mContext)
                    .load(getContactUri(contactId))
                    .placeholder(R.drawable.ic_contact_picture)
                    .into(mViewHolder.contactBadge);

            }
        }
    }
    // endregion

}
Was it helpful?

Solution

Just set the imageview to null in within getView and it should remove what you are experiencing for the most part you'll be right.

The other tiny tiny corner case aspect is that when your asynctask arrives at postExecute, the view might still exist, but it might have already been assigned a different contact to load up (it's been recycled).

You need to put some kind of tag in the viewholder, and then check that it is still the same when you go to set it in postexecute.

To remove the fade in, you need to remove the asynctask from the getview. You need to be able to call picasso within getview, which means having your data ready before arriving at getview.

The below, not quite sure if it will compile, I've done it in a text editor.

But bassically I'm caching results in mCachedContactIds and just reloading the whole table if I need a new one. I've typically found this to be robust. But you can also call the picasso code which I've commented out

public class ConversationThreadsCursorAdapter extends SimpleCursorAdapter {

// region Constants
private static final int RECIPIENT_IDS_COLUMN_INDEX = 3;
private static final int ID2_COLUMN_INDEX = 0;
private static final int ADDRESS_COLUMN_INDEX = 1;
private HashMap<String, Integer> mCachedContactIds = new HashMap<String, Integer>();
// endregion

// region Variables
private final String DEBUG_TAG = getClass().getSimpleName().toString(); 
private Context mContext;
protected Drawable mDefaultPicDrawable;
protected ContentResolver mContentResolver;
protected LinearLayout.LayoutParams mContactPicLayoutParams;
// endregion

// region Constructors
public ConversationThreadsCursorAdapter(Context context, int layout,
Cursor c, String[] from, int[] to, int flags) {
    super(context, layout, c, from, to, flags);
    mContext = context;
    mDefaultPicDrawable = mContext.getResources().getDrawable(
    R.drawable.ic_contact_picture);
    mContactPicLayoutParams = new LinearLayout.LayoutParams(
    mDefaultPicDrawable.getIntrinsicWidth(),
    mDefaultPicDrawable.getIntrinsicHeight());

}
// endregion

public View getView(final int position, View convertView, ViewGroup parent) {
    ViewHolder viewHolder = null;

    if (convertView == null) {
        convertView = mLayoutInflater.inflate(R.layout.simple_message, null);    
        // Creates a ViewHolder and store references to the children
        // views we want to bind data to.
        viewHolder = setupViewHolder(convertView);  
        convertView.setTag(viewHolder);

    } else {
        // Get the ViewHolder back to get fast access to the TextView
        // and the ImageView.
        viewHolder = (ViewHolder) convertView.getTag();
        viewHolder.task.cancel(true);
        viewHolder.contactBadge.setImageDrawable(mDefaultPicDrawable);
    }

    mCursor = getCursor();

    mCursor.moveToPosition(position);

    viewHolder.position = position;

    String recipient_ids = mCursor.getString(RECIPIENT_IDS_COLUMN_INDEX);

    String[] recipients = recipient_ids.split(" ");
    String recipient = recipients[0];

    if(mCachedContactIds.get(recipient) != null){
        Picasso.with(mContext)
            .load(getContactUri(mCachedContactIds.get(recipient)))
            .placeholder(R.drawable.ic_contact_picture)
            .into(mViewHolder.contactBadge);                 
    } else {
        viewHolder.task = new AddressFetcherTask(viewHolder, position);
        viewHolder.task.execute(recipients); 
    }

    return convertView;    
}

// region Helper Methods
private ViewHolder bindUIElements(View convertView) {
    ViewHolder viewHolder = new ViewHolder();    
    viewHolder.contactBadge = (QuickContactBadge) convertView.findViewById(R.id.contact_pic);
    return viewHolder;
}

private ViewHolder setupViewHolder(View convertView) {
    ViewHolder viewHolder = bindUIElements(convertView);    
    viewHolder.contactBadge.setLayoutParams(mContactPicLayoutParams);
    return viewHolder;
}
// endregion

// region Inner Classes

private class ViewHolder {
    QuickContactBadge contactBadge;
    int position;
    AddressFetcherTask task;
}

private class AddressFetcherTask extends AsyncTask < String[], Void, Integer > {
    private ViewHolder mViewHolder;
    private int mPosition;
    private String mRecipient;

    public AddressFetcherTask(ViewHolder viewHolder, int position) {
        mViewHolder = viewHolder;
        mPosition = position;
    }

    @Override
    protected Integer doInBackground(String[]...recipients) {
        mRecipient = recipients[0][0];
        Log.d(DEBUG_TAG, "recipient is " + recipient);
        Cursor c = mContentResolver.query(
        Uri.parse("content://mms-sms/canonical-addresses"), null, "_id = " + mRecipient, null, null);

        String _id = "";
        String address = "";
        while (c.moveToNext()) {
            _id = c.getString(ID2_COLUMN_INDEX);
            address = c.getString(ADDRESS_COLUMN_INDEX);
        }
        c.close();

        int contactId;
        if (address != null) {
            contactId = ContactsUtils.getContactId(mContext, address, "address");
        } else {
            contactId = Integer.valueOf(address);
        }
        return contactId;
    }

    @Override
    protected void onPostExecute(Integer contactId) {                           
        if (mViewHolder.position == mPosition) {
            mCachedContactIds.put(mRecipient, contactId);

            Picasso.with(mContext)
                .load(getContactUri(mCachedContactIds.get(recipient)))
                .placeholder(R.drawable.ic_contact_picture)
                .into(mViewHolder.contactBadge);
        }               
    }
}  
// endregion

}

Or if all that's bugging you left is the fade from picasso, then add noFade() to the request.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top