Mismatched images in table cells with Picasso Library - java

I'm trying to fix an issues that I've seen previously and fixed on iOS, but am unable to fix on android. In iOS, I used the SDWebImage library to download and cache images. However, when scrolling through a long list of cells, the images would come up in the wrong cells. I was able ot fix this by doing the following:
#property (weak) id <SDWebImageOperation> imageOperation;
...
- (void)setFriend:(TAGUser *)friend {
...
self.imageOperation = [[SDWebImageManager sharedManager] downloadWithURL:[NSURL URLWithString:friend.profileImageUrl] options:SDWebImageRetryFailed
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
if(!error) {
self.profileImageView.image = image;
}
}];
...
}
- (void)prepareForReuse {
[super prepareForReuse];
if (self.imageOperation) {
[self.imageOperation cancel];
}
self.imageOperation = nil;
}
On Android, I am using the Picaso Libray and trying to achieve the same result like so:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
if (vi == null) {
vi = inflater.inflate(R.layout.friend_row, parent, false);
ViewHolder holder = new ViewHolder();
holder.friendImage = (ImageView) vi.findViewById(R.id.friend_image);
}
final ViewHolder holder = (ViewHolder) vi.getTag(); //Bad naming convention in my project I know, but it's a built-in method
//THIS SHOULD IN THEORY CANCEL THE REQUEST OF THE OLD FRIEND URL
Picasso.with(mActivity.getBaseContext()).cancelRequest(holder.friendImage);
holder.friendImage.setImageDrawable(mActivity.getResources().getDrawable(R.drawable.background_circle));
final TagUser user = (TagUser) getItem(position);
Picasso.with(mActivity.getBaseContext()).load(user.getProfileUrl()).transform(new RoundedTransformation(ViewUtility.convertPxToDp(mActivity, 23), 0)).fit().into(holder.friendImage,
new Callback() {
#Override
public void onSuccess() {
holder.friendInitials.setVisibility(View.INVISIBLE);
}
#Override
public void onError() {
holder.friendInitials.setVisibility(View.VISIBLE);
}
});
}
Even with cancelRequeset getting called, the profile images are still getting mismatched. Any help would be greatly appreciated!

You don't need to call cancel. Picasso automatically sees when views are being re-used and will cancel old downloads for that image view.
I would also recommend using Picasso's .placeholder API for the background circle.
You seem to be missing a call to setTag when the layout is inflated. Hopefully that's just an error in copying to the post.
Lastly, create the RoundedTransformation once and re-use the same instance for all calls to Picasso.
In the end your code should look like this:
private final Transformation roundTransform;
// Set the following in constructor:
// roundTransform = new RoundedTransformation(ViewUtility.convertPxToDp(mActivity, 23), 0)
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
if (vi == null) {
vi = inflater.inflate(R.layout.friend_row, parent, false);
ViewHolder holder = new ViewHolder();
vi.setTag(holder)
holder.friendImage = (ImageView) vi.findViewById(R.id.friend_image);
}
final ViewHolder holder = (ViewHolder) vi.getTag(); //Bad naming convention in my project I know, but it's a built-in method
final TagUser user = (TagUser) getItem(position);
Picasso.with(mActivity)
.load(user.getProfileUrl())
.placeholder(R.drawable.background_circle)
.transform(roundTransform)
.fit()
.into(holder.friendImage,
new Callback() {
#Override
public void onSuccess() {
holder.friendInitials.setVisibility(View.INVISIBLE);
}
#Override
public void onError() {
holder.friendInitials.setVisibility(View.VISIBLE);
}
});
}

I would advice to use Volley by Google. I have used it in many projects and it never gave mismatched results even in case I had nearly 1000 images in 3 column gridView. Volley

Related

Why is same ListView item reused everytime?

I'm trying to create a simple chat/message layout. My problem is with the Adapter. I want to use two different layouts. One for outgoing and one incoming messages. Furthermore I would like to use the ViewHolder approach. But my implementation causes the same layout to be inflated/reused everytime getView runs.
Can anyone please explain to me why this is happening? Do I need to use two viewholders?
The relevant code is shown below
#Override
public View getView(int position, View convertView, ViewGroup parent) {
int direction = getItemViewType(position);
ViewHolder holder;
if (convertView == null) {
int res = 0;
int resid = 0;
holder = new ViewHolder();
if (direction == DIRECTION_INCOMING) {
holder = new ViewHolder();
res = R.layout.message_l_box;
resid = R.id.left_bubble_text;
}
else {
res = R.layout.message_r_box;
resid = R.id.right_bubble_text;
}
convertView = LayoutInflater.from(mContext).inflate(res, parent, false);
holder.text = (TextView) convertView.findViewById(resid);
convertView.setTag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
holder.text.setText(getItem(position).first);
return convertView;
}
// Ensure that find by id is not called every time -> could cause slow scrolling
private class ViewHolder {
TextView text;
}
ListViews reuse Views when possible for performance. That is what the convertView View in your code is for. If Android already had another row that you scrolled away from it can be reused so it will be passed into your getView() method. Your code is not creating a new layout if convertView is not null.
You could do something like this, basically store the current layouts direction in the ViewHolder so you know if you can reuse the layout or have to re-inflate it.
private class ViewHolder {
TextView text;
int direction;
}
if (convertView == null || ((ViewHolder) convertView.getTag()).direction != direction) {
// same code you have now to layout a new row
holder.direction = direction
} else { // we know the existing view has same direction so it has the correct layout already inflated and we can reuse it
holder = (ViewHolder) convertView.getTag();
}

setProgressbar not updating inside getView form a thread running on activity?

I have a listview with each item having a ProgressBar on top. My own created thread running and downloading the data in background and have to show download progress in getView part for each item, but i'm not able to update the setProgressbar in getView method of adapter.
But when user scrolls up and down(away from particular view) then its update the progress and freezes and not increasing based on download percentage.
My thread code
downloading = Ion.with(LoadTumblrActivity.this)
.load(item.getGifUrl())
// can also use a custom callback
// can also use a custom callback
.progress(new ProgressCallback() {
#
Override
public void onProgress(long downloaded, long total) {
float progressVal = ((float) downloaded / (float) total);
item.setProgress((int)(100 * progressVal));
}
})
.write(
new File(Environment.getExternalStorageDirectory().getPath() + "/sdcard" + fileName))
.setCallback(new FutureCallback < File > () {
#
SuppressLint("SdCardPath")# Override
public void onCompleted(Exception e, File file) {
{
if (e != null) {
Toast.makeText(LoadTumblrActivity.this, "Error downloading file", Toast.LENGTH_LONG).show();
System.out.println(e);
return;
}
}
}
});
My BaseAdapter
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
// reuse views
if (rowView == null) {
LayoutInflater inflater = activity.getLayoutInflater();
rowView = inflater.inflate(R.layout.feed_item, parent, false);
// configure view holder
ViewHolder viewHolder = new ViewHolder();
viewHolder.progressBar = (ProgressBar) rowView.findViewById(R.id.progress);
rowView.setTag(viewHolder);
}
holder.progressBar.setProgress(item.getProgress());
}
Your Problem is that you call
holder.progressBar.setProgress(item.getProgress());
inside your getView and getView is only called for the items visible on the Screen. That's why your ProgressBar only updates when you scroll for the Items which Comes to Screen due to scrolling! You Need to call this line outside of the getView for example in an AsyncTask when you call publishProgress(); in onProgressUpdate to give user a real experience of how Long it takes till the item appears/finished downloading.
Another Approach would be to set the progressBar to intermediate= "false" and only Show a spinning progressbar! ;)
Hope it helps
Use runOnuiThread to update progress bar:
runOnUiThread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
progressDialog.setMessage(ii+" Apps Loaded...");
ii++;
}
});
Source code from Helper+

Android checkbox Listview recycling is causing unexpected box checking

I am developing an app using a ListView with a simple custom adapter, each row containing a CheckBox object. However, due to ListView's recycling feature (that I don't plan on turning off), when any of the boxes are checked, others below or above in the ListView are also checked.
The following is my getView() in the adapter, along with the custom ViewHolder class:
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
LayoutInflater mInflater = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = mInflater.inflate(R.layout.populate_friends_row, null);
holder = new ViewHolder();
holder.nameCheckBox = (CheckBox) convertView.findViewById(R.id.isFriend);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.nameCheckBox.setText(data.get(position).contactLabel);
holder.nameCheckBox.setChecked(checked.get(position));
holder.nameCheckBox.setTag(String.valueOf(position)); // to properly track the actual position
holder.nameCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener(){
#Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
int pos = Integer.parseInt(buttonView.getTag().toString());
checked.set(pos, isChecked);
}
});
return convertView;
}
public static class ViewHolder {
public CheckBox nameCheckBox;
}
I am already holding the checked boxes in the ArrayList of booleans: checked.
Any help would be appreciated, thank you.
When you're calling holder.nameCheckBox.setChecked(checked.get(position)); to configure the view to be displayed for this view, the listener is called while the tag still has the position of the previous checkbox.
Try removing the listener (setting it to null) before calling setChecked

Issue with custom gridview Android

I have developed an app in which I display data in Gridview. All data comes from local storage. I am able to display each data correctly and there are no issues with it. But when I have scroll the Gridview and goto the bottom and getback to Top,it changes position. And sometimes when I scroll down and getback to Top, a blank screen appears on screen;no data found at all!
So I thought that there is issue with getView(). I am unable to figure out the problem
Code of getView():
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder viewholder;
// LayoutInflater inflator = activit.getLayoutInflater();
if(convertView == null) {
// recycled view is null so create it.
viewholder = new ViewHolder();
convertView = inflator.inflate(R.layout.gridviewrow, null);
viewholder.imgvGridicon = (ImageView) convertView.findViewById(R.id.imgvGridicon);
viewholder.txtGridItemlabel = (TextView) convertView.findViewById(R.id.txtGridItemlabel);
convertView.setTag(viewholder);
} else {
viewholder = (ViewHolder) convertView.getTag();
}
if ((lstpinfo.get(position).appname.toString()) != null) {
viewholder.imgvGridicon.setImageDrawable((lstpinfo.get(position).icon));
viewholder.txtGridItemlabel.setText(lstpinfo.get(position).appname.toString());
}
return convertView;
}
Update::
Intitalize of inflater::
private LayoutInflater inflator;
private ArrayList<PInfo> lstpinfo = new ArrayList<PInfo>();
public GridViewAdapter(Context cntx, ArrayList<PInfo> lstpinfo) {
activit = cntx;
inflator = LayoutInflater.from(cntx);
this.lstpinfo = lstpinfo;
}
You have to set your view height with fixed value. There's a scrolling bug in android gridview with different heights. Please see this for reference.

GreenDroid PagedView - Multiple pages (different layout) help.. Android SDK

I've spent the day playing around with the GreenDroid PagedView and PagedAdaptor to create multiple screens that one can swipe through (a la iPhone style and Android Home Screen style). It's a very cool effect and the GreenDroid library is awesome. Soo.. I can get this working for one XML layout (code below), but what I am attempting to do is have three screens with different layouts. I've played around and got it working successfully with one method, but am unsure whether the method I'm using will cause problems due to repeat inflation of XML layout at every method call. I'll give some examples of what I've tried to save time. I'm hoping someone reads this that has had experience with GreenDroid and can point me in the right direction. Maybe this will also help someone else too..
Here is my code:
HomePageActivity.java
public class HomePageActivity extends GDActivity {
private static final int PAGE_COUNT = 3;
private final Handler mHandler = new Handler();
private PageIndicator mPageIndicator;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setActionBarContentView(R.layout.homepage_view); // container xml
final PagedView pagedView = (PagedView) findViewById(R.id.homepage_view);
pagedView.setOnPageChangeListener(mOnPagedViewChangedListener);
pagedView.setAdapter(new HomePageAdapter());
mPageIndicator = (PageIndicator) findViewById(R.id.page_indicator);
mPageIndicator.setDotCount(PAGE_COUNT);
setActivePage(pagedView.getCurrentPage());
}
private void setActivePage(int page) {
mPageIndicator.setActiveDot(page);
}
private OnPagedViewChangeListener mOnPagedViewChangedListener = new OnPagedViewChangeListener() {
#Override
public void onStopTracking(PagedView pagedView) {
}
#Override
public void onStartTracking(PagedView pagedView) {
}
#Override
public void onPageChanged(PagedView pagedView, int previousPage, int newPage) {
setActivePage(newPage);
}
};
private class HomePageAdapter extends PagedAdapter {
#Override
public int getCount() {
return PAGE_COUNT;
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
Log.i("getView","ConvertView is null");
convertView = getLayoutInflater().inflate(R.layout.homepage_one, parent, false);
}
Log.i("getView Page",Integer.toString(position));
return convertView;
}
}
The two lines:
Log.i("getView","ConvertView is null");
Log.i("getView Page",Integer.toString(position));
I am using to debug this to see what is happening via. LogCat and so I can experiment.
The first line is displayed ONCE - first time application is created. I've noticed that VERY VERY occasionally as I swipe through the pages (I have three - set by variable PAGE_COUNT at top), that after 30-50 swipes I will see this message again. This shows that this layout is only INFLATED once - for all screens.
The second line I naturally see every time I change page. The integer position is either 0, 1 or 2 in this particular case.
The XML layout homepage_one is a simple layout with three TextViews. The XML layout homepage_view is the container for these pages.
homepage_view.xml
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:greendroid="http://schemas.android.com/apk/res/com.cyrilmottier.android.gdcatalog">
<greendroid.widget.PagedView
android:id="#+id/homepage_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/splash" />
<greendroid.widget.PageIndicator
android:id="#+id/page_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal"
android:paddingTop="10dp" />
Naturally I've tried some obvious methods such as this: (which works as wanted)
public View getView(int position, View convertView, ViewGroup parent) {
if (position == 0) {
convertView = getLayoutInflater().inflate(R.layout.homepage_one, parent, false);
} else if( position == 1 ){
convertView = getLayoutInflater().inflate(R.layout.homepage_two, parent, false);
} else {
convertView = getLayoutInflater().inflate(R.layout.homepage_three, parent, false);
}
return convertView;
}
This works as intended. Every screen is different as I want. I've not noticed any performance problems during testing, but it's worth noting in the original instance that we only inflate the layout ONCE. As this method is called (which is every time the page changes, we inflate the layout again. This doesn't seem resourceful, but then again this is my first week with Android development and I don't know how the architecture works... (just reading, reading, reading and testing, testing, testing) - so hence why I would like to hear from more experienced and knowledgeable developers. This may well be acceptable.. I would like to hear from you.
I tried this:
public View getView(int position, View convertView, ViewGroup parent) {
if (PAGE_ONE == false && position == 0) {
convertView = getLayoutInflater().inflate(R.layout.homepage_one, parent, false);
PAGE_ONE = true;
} else if(PAGE_TWO == false && position == 1 ){
convertView = getLayoutInflater().inflate(R.layout.homepage_two, parent, false);
PAGE_TWO = true;
} else if( PAGE_THREE == false && position == 2 ){
convertView = getLayoutInflater().inflate(R.layout.homepage_three,parent, false);
PAGE_THREE = true;
}
return convertView;
}
PAGE_ONE, PAGE_TWO, PAGE_THREE were set in the class as private boolean = false. The consideration behind this is that we only inflate layout once and every other call to this method simply returns the already inflated xml layout. Unfortunately this crashes my emulator and my phone (HTC EVO 3D). As Jay-Z would say, on to the next one...
Any ideas guys?
The GreenDroid's PagedView is normally intended to display a large set of pages. It can be considered as an horizontal paged ListView. This is the reason why using it is very close to the common ListAdapter.
In your case, you want to keep three pages in memory. This is completely possible but not recommended as you are preventing the PagedView from recycling your pages and also increasing the amount of View in memory.
Having only 3 pages (with a relatively flat and narrow View hierarchy) is OK. A better way do to what you are trying to do would be to lazily-initialize the pages and keep a reference on each pages.
private static final int PAGE_COUNT = 3;
private View[] mPages = new View[PAGE_COUNT];
public View getView(int position, View convertView, ViewGroup parent) {
if (mPages[position] == null) {
View page = createPageAtPosition(position, parent);
mPages[position] = page;
}
return mPages[position];
}
Of course you may have a warning in your logs as the PagedView notify the developer when the convertView is not reused.
I ran into a similar issue as well. Not sure if it is a bug with GreenDroid or user error on my part. (The latter is more likely)
Anyway, here is the solution I came up with. Your solution was nearly there, but you missed the critical 'else' condition when grabbing an existing view. It's working quite well for me.
/** get the view to display */
#Override
public View getView(int position, View convertView, ViewGroup parent) {
pageEnum currentPage = pageEnum.values()[position];
if(null == mViews[position]) {
switch(currentPage) {
case PAGE_1:
convertView = getLayoutInflater().inflate(R.layout.page1, parent, false);
break;
case PAGE_2L:
convertView = getLayoutInflater().inflate(R.layout.page2, parent, false);
break;
case PAGE_3:
convertView = getLayoutInflater().inflate(R.layout.page3, parent, false);
break;
default:
convertView = null;
Log.e(TAG, "Invalid page.");
break;
} /* switch(currentPage) */
mViews[position] = convertView;
} /* if(null == mViews[position]) */
else {
convertView = mViews[position];
}
return convertView;
} /* getView() */

Categories