Ion - Images failing to load into recyclerview - android

I have been pulling my hair out over this bug as it isn't exactly reproducible. I have a custom recycler adapter which loads values from a database. It calls a network helper class to build a URL and load an image using Ion. This bug doesn't appear to be affected by scroll speed, but I think it may be affected by the amount of image calls made to the server at once.
public ThreadAdapter(Cursor cursor) {
super(cursor);
}
#Override
public void onBindViewHolderCursor(RecyclerView.ViewHolder holder, Cursor cursor) {
ThreadViewHolder threadViewHolder = (ThreadViewHolder)holder;
networkHelper.getImage(threadViewHolder.image,
cursor.getString(cursor.getColumnIndex(DbContract.ThreadEntry.COLUMN_THREAD_IMAGE_NAME),
cursor.getString(cursor.getColumnIndex(DbContract.ThreadEntry.COLUMN_THREAD_IMAGE_EXTENSION))
);
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.from(parent.getContext()).inflate(R.layout.card_thread, parent, false);
ThreadViewHolder viewHolder = new ThreadViewHolder(view);
return viewHolder;
}
class ThreadViewHolder extends RecyclerView.ViewHolder{
public ImageView image;
public ThreadViewHolder(View itemView) {
super(itemView);
image = (ImageView) itemView.findViewById(R.id.thread_image);
}
}
https://gist.github.com/Shywim/127f207e7248fe48400b
public void getImage(ImageView imageView, String name, String extension){
if (name != null && extension != null){
String imageUrl = "example.com/" + name + extension;
Log.d(LOG_TAG, "Image Url " + imageUrl);
Ion.with(imageView)
.fitCenter()
.placeholder(R.drawable.ic_launcher)
.error(R.drawable.error)
.load(imageUrl);
}else {
imageView.setImageBitmap(null);
}
}
It appears to be building the URL correctly and only calling Ion when something needs to be called. I sometimes will see the placeholder image appear and then disappear. I never have seen the error image appear at all. I think if item A, item B and item C all have images that need to be loaded and all appear at the same time, the odds are greater that it will fail loading them.

Related

Making my RecyclerView perform better

My app is all about performance, so I would really like to optimize this RecyclerView as much as possible. I have measured how long every part takes to complete, and the whole thing needs about 150ms to load. Here is the RELEVANT code:
public class AppAdapter extends RecyclerView.Adapter<AppAdapter.TabViewHolder> {
private Context context;
private MenuActivity menuActivity;
private Intent intent;
private ArrayList<String> stringys = new ArrayList<>();
public void setUpAdapter(Context mContext, MenuActivity mMenuActivity, Intent mIntent, ArrayList<String> mString) {
this.menuActivity = mMenuActivity;
this.context = mContext;
this.intent = mIntent;
stringys.addAll(mString);
}
#Override
public TabViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(context);
Log.d(TAG, "CreatingViewholder " + "Time: " + menuActivity.deltaTime());
View view = inflater.inflate(R.layout.item_tab, parent, false); //Here is where the wait happens
Log.d(TAG, "ViewHolderCreated " + "Time: " + menuActivity.deltaTime());
return new TabViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull TabViewHolder holder, int position) {
holder.bind(position, holder);
}
#Override
public int getItemCount() {
return stringys.size();
}
class TabViewHolder extends RecyclerView.ViewHolder {
TextView text;
ImageView image;
TabViewHolder(View itemView) {
super(itemView);
text = itemView.findViewById(R.id.label);
image = itemView.findViewById(R.id.icon);
}
void bind(int position, TabViewHolder holder) {
new LongOperation(text, image).execute(stringys.get(position));
}
}
private class LongOperation extends AsyncTask<String, Void, Void> {
TextView text;
ImageView image;
CharSequence textToSet;
Drawable imageToSet;
public LongOperation(TextView text, ImageView image) {
super();
this.text = text;
this.image = image;
}
#Override
protected Void doInBackground(String... params) {
textToSet = params[0].getTitle(context.getDefaultSharedPreferences());
imageToSet = params[0].getIcon(context.getDefaultSharedPreferences());
return null;
}
#Override
protected void onPostExecute(Void result) {
text.setText(textToSet);
image.setImageDrawable(imageToSet);
}
}
}
I have several ideas/questions about this:
Is it possible to reuse this ViewHolder? I'm using it every time, and each inflation takes about 5ms, which adds up quickly because this is a grid, and I have about 40 holders loading when I launch this.
If it helps, I am also ready to use another kind of view. I took recyclerview as it made the most sense imo, but if there is a better-performing view I can change to that.
Would it help if I used a Linear Layout, and put 4 of the ViewHolders I currently use next to each other? Would my time be then reduced by 4?
In the asynctask, I call context.getDefaultSharedPreferences() twice. Would it load faster (the async part) if I did it once and had it as a variable to pass?
The asynctask it might leak if it isn't static. I assume that's not a problem because it finished very quickly anyways, right?
RecyclerView is used for long lists, implementation of ViewHolder pattern improves performance by re-using Views after they leave the screen. If you have don't have enough items to fill the viewport there's no reason to use it.
Also keep in mind while debugging setting bitmaps is much slower, if possible try building as "release" and see how it performs.
Your ViewHolder will be reused as you scroll down, but you need at least enough view holders to display what is currently on screen, plus a few more that are just off screen. So when you first load this are there 40 views displayed on the screen? If not, you have an issue.
It depends if you can scroll your view a lot.
You might get a slight improvement but I doubt it.
Shared preferences calls are cached. You don't have to worry about them taking a long time after the first access.
Not sure
Other improvements:
You could move your LayoutInflater creation to setUpAdapter()
What is in R.layout.item_tab? Make sure your hierarchy of views is as flat as possible. Use Constraint Layout if you can.

Android listview with Glide - doubled bitmaps after load

I'm developing an android application. One of my fragments contains a simple listview showing friend list. Each friend can have its own profile image - it is set by the Glide library. When user has no profile pic set the default image is shown. My problem is, that every time, first element on the list gets the same picture which is set on the last element of the list which is not default picture. What i mean is shown on pic:
user with name wiktor has set profile picture and as you see the first position bonzo has wiktor's profile pic ( bonzo should have default pic )
there is also a problem with deleting user form list:
as you see, i removed majka from friend list and next elements gets her picture.
The default profile picture is set in inside row layout xml from drawables.
Here is code of my listview adapter:
public class FriendsAdapter extends ArrayAdapter<FriendData> {
static class FriendHolder {
TextView friendName;
TextView friendRank;
ImageView friendIcon;
ImageButton deleteFriendBtn;
ImageButton banFriendBtn;
}
private List<FriendData> list;
public FriendsAdapter(Context context, int resource, List<FriendData> objects) {
super(context, resource, objects);
list = objects;
}
#Override
public View getView(final int position, View convertView, final ViewGroup parent) {
final FriendData element = getItem(position);
final FriendHolder viewHolder;
if (convertView == null) {
viewHolder = new FriendHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.friend_layout, parent, false);
viewHolder.friendIcon = (ImageView) convertView.findViewById(R.id.friendIcon);
viewHolder.friendName = (TextView) convertView.findViewById(R.id.friendName);
viewHolder.friendRank = (TextView) convertView.findViewById(R.id.friendRank);
viewHolder.deleteFriendBtn = (ImageButton) convertView.findViewById(R.id.deleteFriendBtn);
viewHolder.banFriendBtn = (ImageButton) convertView.findViewById(R.id.banFriendBtn);
convertView.setTag(viewHolder);
} else {
viewHolder = (FriendHolder) convertView.getTag();
}
if (element.getPhoto() != null) {
String photo = S3ImageHandler.SMALL_PROFILE_ICON_PREFIX + element.getPhoto();
String url = String.format(S3ImageHandler.AMAZON_PROFILE_DOWNLOAD_LINK, photo);
Glide.with(getContext())
.load(url)
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.placeholder(R.drawable.user_small)
.into(new BitmapImageViewTarget(viewHolder.friendIcon) {
#Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable = RoundedBitmapDrawableFactory.create(getContext().getResources(), resource);
circularBitmapDrawable.setCircular(true);
viewHolder.friendIcon.setImageDrawable(circularBitmapDrawable);
}
});
}
viewHolder.friendName.setText(element.getId());
viewHolder.friendRank.setText(String.format("%s %d", getContext().getString(R.string.text_rank), element.getRank()));
viewHolder.deleteFriendBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
confirmDelete(element, position, parent);
}
});
viewHolder.banFriendBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
confirmBan(element, position, parent);
}
});
return convertView;
}
removing user from friend list:
remove(element);
notifyDataSetChanged();
does any of you see what i do wrong ? i would be very grateful for some help. thank you :)
You're resuing list items (by definition of recycler view) which means that if an image was set to an item and you don't clear it, the image will remain there. So even though setText changes the labels viewHolder.friendIcon is not touched. The fix is really simple:
if (element.getPhoto() != null) {
Glide.with(getContext())...
} else {
Glide.clear(viewHolder.friendIcon); // tell Glide that it should forget this view
viewHolder.friendIcon.setImageResource(R.drawable.user_small); // manually set "unknown" icon
}
Also remove the drawable from the xml, or at least change to tools:src which will help reducing the inflation time; the value is overwritten by Glide every time anyway.
To reduce complexity there's an alternative:
class FriendData {
// if you can't modify this class you can also put it in a `static String getPhotoUrl(Element)` somewhere
public void String getPhotoUrl() {
if (this.getPhoto() == null) return null;
String photo = S3ImageHandler.SMALL_PROFILE_ICON_PREFIX + this.getPhoto();
String url = String.format(S3ImageHandler.AMAZON_PROFILE_DOWNLOAD_LINK, photo);
return url;
}
}
and then replace the whole if (element.getPhoto() != null) {...} with:
Glide.with(getContext())
.load(element.getPhotoUrl()) // this may be null --\
.asBitmap() // |
.diskCacheStrategy(DiskCacheStrategy.ALL) // |
.placeholder(R.drawable.user_small) // |
.fallback(R.drawable.user_small) // <--------------/
.into(new BitmapImageViewTarget(viewHolder.friendIcon) { ... })
;
This will also result in proper behavior because even though there's no image url Glide will take care of setting something, see JavaDoc or source of fallback.
As a sidenote also consider using CircleCrop. Aside from caching benefits it would also support GIFs because you can remove the .asBitmap() and the custom target.

RecyclerView adapter showing wrong images

I have a RecyclerView adapter that looks like this:
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {
private static Context context;
private List<Message> mDataset;
public RecyclerAdapter(Context context, List<Message> myDataset) {
this.context = context;
this.mDataset = myDataset;
}
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener, View.OnClickListener {
public TextView title;
public LinearLayout placeholder;
public ViewHolder(View view) {
super(view);
view.setOnCreateContextMenuListener(this);
title = (TextView) view.findViewById(R.id.title);
placeholder = (LinearLayout) view.findViewById(R.id.placeholder);
}
}
#Override
public RecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_layout, parent, false);
ViewHolder vh = new ViewHolder((LinearLayout) view);
return vh;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Message item = mDataset.get(position);
holder.title.setText(item.getTitle());
int numImages = item.getImages().size();
if (numImages > 0) {
View test = LayoutInflater.from(holder.placeholder.getContext()).inflate(R.layout.images, holder.placeholder, false);
ImageView image = (ImageView) test.findViewById(R.id.image);
Glide.with(context)
.load("http://www.website.com/test.png")
.fitCenter()
.into(image);
holder.placeholder.addView(test);
}
}
#Override
public int getItemCount() {
return mDataset.size();
}
}
However, some of the items in the RecyclerView are showing images when they shouldn't be. How can I stop this from happening?
I do the check if (numImages > 0) { in onBindViewHolder(), but that's still not stopping it from showing images for items that shouldn't have images.
You should set imageView.setImageDrawable (null)
In onBindViewHolder() before setting the image using glide.
Setting image drawable to null fix the issue.
Hope it helps!
The problem is in onBindViewHolder, here:
if (numImages > 0) {
View test = LayoutInflater.from(holder.placeholder.getContext()).inflate(R.layout.images, holder.placeholder, false);
ImageView image = (ImageView) test.findViewById(R.id.image);
Glide.with(context)
.load("http://www.website.com/test.png")
.fitCenter()
.into(image);
holder.placeholder.addView(test);
}
If numImages is equal to 0, you're simply allowing the previously started load into the view you're reusing to continue. When it finishes, it will still load the old image into your view. To prevent this, tell Glide to cancel the previous load by calling clear:
if (numImages > 0) {
View test = LayoutInflater.from(holder.placeholder.getContext()).inflate(R.layout.images, holder.placeholder, false);
ImageView image = (ImageView) test.findViewById(R.id.image);
Glide.with(context)
.load("http://www.website.com/test.png")
.fitCenter()
.into(image);
holder.placeholder.addView(test);
} else {
Glide.clear(image);
}
When you call into(), Glide handles canceling the old load for you. If you're not going to call into(), you must call clear() yourself.
Every call to onBindViewHolder must include either a load() call or a clear() call.
I also had issues with RecyclerView showing wrong images. This happens because RecyclerView is not inflating view for every new list item: instead list items are being recycled.
By recycling views we can ruffly understand cloning views. A cloned view might have an image set from the previous interaction.
This is especially fair if your are using Picasso, Glide, or some other lib for async loading. These libs hold reference to an ImageView, and set an image on that refference when image is loaded.
By the time the image gets loaded, the item view might have gotten cloned, and the image is going to be set to the wrong clone.
To make a long story short, I solved this problem by restricting RecyclerView from cloning my item views:
setIsRecyclable(false)in ViewHolder constructor.
Now RecyclerView is working a bit slower, but at least the images are set right.
Or else cansel loading image in onViewRecycled(ViewHolder holde)
The issue here is that, as you are working with views that are going to be recycled, you'll need to handle all the possible scenarios at the time your binding your view.
For example, if you're adding the ImageView to the LinearLayout on position 0 of the data source, then, if position 4 doesn't met the condition, its view will most likely have the ImageView added when binding position 0.
You can add the content of R.layout.images content inside your
R.layout.message_layout layout's R.id.placeholder and showing/hiding the placeholder depending on the case.
So, your onBindViewHolder method would be something like:
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Message item = mDataset.get(position);
holder.title.setText(item.getTitle());
int numImages = item.getImages().size();
if (numImages > 0) {
holder.placeholder.setVisivility(View.VISIBLE);
ImageView image = (ImageView)holder.placeholder.findViewById(R.id.image);
Glide.with(context)
.load("http://www.website.com/test.png")
.fitCenter()
.into(image);
}else{
holder.placeholder.setVisibility(View.INVISIBLE);
}
}
Sometimes when using RecyclerView, a View may be re-used and retain the size from a previous position that will be changed for the current position. To handle those cases, you can create a new [ViewTarget and pass in true for waitForLayout]:
#Override
public void onBindViewHolder(VH holder, int position) {
Glide.with(fragment)
.load(urls.get(position))
.into(new DrawableImageViewTarget(holder.imageView,/*waitForLayout=*/ true));
https://bumptech.github.io/glide/doc/targets.html
I also had the same problem and ended with below solution and it working fine for me..
Have your hands on this solution might be work for you too (Put below code in your adapter class)-
If you are using Kotlin -
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemViewType(position: Int): Int {
return position
}
If you are using JAVA -
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
This works for me in onBindViewHolder!
if(!m.getPicture().isEmpty())
{
holder.setIsRecyclable(false);
Picasso.with(holder.profile_pic.getContext()).load(m.getPicture()).placeholder(R.mipmap.ic_launcher_round).into(holder.profile_pic);
Animation fadeOut = new AlphaAnimation(0, 1);
fadeOut.setInterpolator(new AccelerateInterpolator());
fadeOut.setDuration(1000);
holder.profile_pic.startAnimation(fadeOut);
}
else
{
holder.setIsRecyclable(true);
}
I was having same issue I solved by writing holder.setIsRecyclable(false).Worked for me.
#Override
public void onBindViewHolder(#NonNull RecylerViewHolder holder, int position) {
NewsFeed currentFeed = newsFeeds.get(position);
holder.textView.setText(currentFeed.getNewsTitle());
holder.sectionView.setText(currentFeed.getNewsSection());
if(currentFeed.getImageId() == "NOIMG") {
holder.setIsRecyclable(false);
Log.v("ImageLoad","Image not loaded");
} else {
Picasso.get().load(currentFeed.getImageId()).into(holder.imageView);
Log.v("ImageLoad","Image id "+ currentFeed.getImageId());
}
holder.dateView.setText(getModifiedDate(currentFeed.getDate()));
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemViewType(position: Int): Int {
return position
}
This Works for Me
I Had the same issue and i fixed it like this:
GOAL : onViewAttachedToWindow
#Override
public void onViewAttachedToWindow(Holder holder) {
super.onViewAttachedToWindow(holder);
StructAllItems sfi = mArrayList.get(position);
if (!sfi.getPicHayatParking().isEmpty()) {
holder.viewFliperMelk.addSlider(new TextSliderView(mContext.getApplicationContext()).image(T.GET_MELK_IMAGE + '/' + sfi.getPicHayatParking() + ".jpg").setScaleType(BaseSliderView.ScaleType.CenterCrop));
}
if (!sfi.getPicSleepRoom().isEmpty()) {
holder.viewFliperMelk.addSlider(new TextSliderView(mContext.getApplicationContext()).image(T.GET_MELK_IMAGE + '/' + sfi.getPicSleepRoom() + ".jpg").setScaleType(BaseSliderView.ScaleType.CenterCrop));
}
if (!sfi.getPicSalonPazirayi().isEmpty()) {
holder.viewFliperMelk.addSlider(new TextSliderView(mContext.getApplicationContext()).image(T.GET_MELK_IMAGE + '/' + sfi.getPicSalonPazirayi() + ".jpg").setScaleType(BaseSliderView.ScaleType.CenterCrop));
}
if (!sfi.getPicNamayeStruct().isEmpty()) {
holder.viewFliperMelk.addSlider(new TextSliderView(mContext.getApplicationContext()).image(T.GET_MELK_IMAGE + '/' + sfi.getPicNamayeStruct() + ".jpg").setScaleType(BaseSliderView.ScaleType.CenterCrop));
}
}
I had a similar issue when getting pictures from the photo gallery and putting them in a recyclerview with GridLayoutManager(never had the issue with Glide). So in the adapter onBindViewHolder use a HashMap or SparseIntArray to put the current hashcode(this is the common thing that the recycled views have in common) and adapter position inside it. Then call your background task and then once it's done and before you set the image, check to see if the hashcode key - which will always have the current adapter position as the value - still has the same value (adapter position) as when you first called the background task.
(Global variable)
private SparseIntArray hashMap = new SparseIntArray();
onBindViewHolder(ViewHolder holder, int position){
holder.imageView.setImageResource(R.drawable.grey_square);
hashMap.put(holder.hashCode(), position);
yourBackgroundTask(ViewHolder holder, int position);
}
yourBackGroundTask(ViewHolder holder, int holderPosition){
do some stuff in the background.....
*if you want to stop to image from downloading / or in my case
fetching the image from MediaStore then do -
if(hashMap.get(holder.hashCode())!=(holderPos)){
return null;
}
- in the background task, before the call to get the
image
onPostExecute{
if(hashMap.get(holder.hashCode())==(holderPosition)){
holder.imageView.setImageBitmap(result);
}
}
}
So i am just providing an extension to this answer since there is not much space to leave it as comment.
After trying out like mentioned in one of above solutions i found out that, the real issue can still be addressed even if you are using a static resource(is not being downloaded and is available locally)
So basically on onBindViewHolder event i just converted the resource to drawable and added it like below :
imageView.setImageDrawable(ContextCompat.getDrawable(context,R.drawable.album_art_unknown));
this way you wont have an empty space on the view while glide/async downloader is loading the actual image from network.
plus looking at that being reloaded every time i also added below code while calling the recycler adapter class;
recyclerView.setItemViewCacheSize(10);
recyclerView.setDrawingCacheEnabled(true);
so by using above way you wont need to set setIsRecyclable(false) which is degrading if you have larger datasets.
By doing this i you will have a flicker free loading of recyclerview of course except for the initial loads.
I would like to say that if you send the ImageView and any load-async command (for instance loading from S3), the recycler view does get confused.
I did set the bitmap null in the onViewRecycled and tested with attach and detach views etc. the issue never went away.
The issue is that if a holderView gets used for image-1, image-10 and stops at the scroll with image-19, what the user sees is image-1, then image-10 and then image-19.
One method that worked for me is to keep a hash_map that helps know what is the latest image that needs to be displayed on that ImageView.
Remember, the holder is recycled, so the hash for that view is persistent.
1- Create this map for storing what image should be displayed,
public static HashMap<Integer, String> VIEW_SYNCHER = new HashMap<Integer, String>();
2- In your Adapter, onBindViewHolder,
String thumbnailCacheKey = "img-url";
GLOBALS.VIEW_SYNCHER.put(holder.thumbnailImage.hashCode(), thumbnailCacheKey);
3- Then you have some async call to make the network call and load the image in the view right ?
In that code after loading the image from S3, you test to make sure what goes into the View,
// The ImageView in the network data loader, get its hash.
int viewCode = iim.imView[0].hashCode();
if (GLOBALS.VIEW_SYNCHER.containsKey(viewCode))
if (GLOBALS.VIEW_SYNCHER.get(viewCode).equals(bitmapKey))
iim.imView[0].setImageBitmap(GLOBALS.BITMAP_CACHE.get(bitmapKey).bitmapData);
So essentially, you make sure what is the last image key that should go into a view, then when you download the image you check to make sure that's the last image URL that goes in that view.
This solution worked for me.

How to avoid resetting the whole view after loading an imageview in Android?

I'm working on an Android project with a lot image loading from a remote server.
I'm using this utility for downloading the images:
http://code.google.com/p/android-imagedownloader/
The main issue is when any image download finishes, the whole Screen would seem to reset.
Along with the view reset the position of the animated UI controls resets too.
That code is based on an article from two years ago and the Android Developers have since given much better information and methods for handling ASync images within a ListView Adaptewr.
Ideally you should be implementing an ImageDownload class or some sorts and using the notifyDataSetChanged(); call on your ListViewAdpater to have the View updated correctly.
Create an ImageLoadedCallback:
// Interfaces
public interface ImageLoadedCallback {
public void imageLoaded(Drawable imageDrawable, String imageUrl);
}
Implement it on your ListAdapter:
All this code is doing is getting the next item to display in the List and then looking to see if we have the image available, if we do - set it. If not, send away our ASync request to load it and then let the Adapter know that it's ready.
public class ArticleAdapter extends SimpleCursorAdapter implements ImageLoadedCallback {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(getCursor().moveToPosition(position)) {
ViewHolder viewHolder;
if(convertView == null) {
convertView = inflater.inflate(R.layout.article_list_item, null);
viewHolder = new ViewHolder();
viewHolder.image = (ImageView) convertView.findViewById(R.id.imgArticleImage);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
String image = getCursor().getString(getCursor().getColumnIndex("thumbURL"));
if(imgCache.hasImage(image)) {
viewHolder.image.setImageDrawable(imgCache.loadDrawable(image, this));
} else {
imgCache.loadDrawable(image, this);
}
}
return convertView;
}
public void imageLoaded(Drawable imageDrawable, String imageUrl) {
this.notifyDataSetChanged();
}
}

Wrong Image Load Order In Dynamic List View - Android

I am trying to load images dynamically in a list view in android. The code below loads the image according to the viewed position and loads the image in that array position. However, as it gets the data in background, sometimes the image in the first array is loaded in the second imageview position, second in third etc. I guess when the getDataInBackground part of the first item in the array is already finished the sytem is trying to create the second cell at that time and loads it to the second cell. How can I handle this, I am using android studio AVD with version kitkat and nexus 5 emulator.
private class MyListAdapter extends ArrayAdapter<String> {
public MyListAdapter() {
super(getActivity().getApplicationContext(), R.layout.fragment_users_cell, myItemList);
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View cellView = convertView;
if (cellView == null){
cellView = getActivity().getLayoutInflater().inflate(R.layout.fragment_users_cell, parent, false);
}
cellProfileImage = (ImageView) cellView.findViewById(R.id.fragment_users_cell_profileImg);
System.out.println("The current position" + position);
if (resultsImageFiles.get(position)==null) {
Bitmap image = BitmapFactory.decodeResource(getActivity().getResources(), R.drawable.ph2);
cellProfileImage.setImageBitmap(image);
} else {
ParseFile file = resultsImageFiles.get(position);
file.getDataInBackground(new GetDataCallback() {
#Override
public void done(byte[] data, ParseException e) {
if (e == null) {
Bitmap image = BitmapFactory.decodeByteArray(data, 0, data.length);
cellProfileImage.setImageBitmap(image);
}
}
});
}
return cellView;
}
}
Listview reuse convertview --> Some itemView has same value for view. In this case, you have to remove cellProfileImage before Bitmap loaded.
try to remove all cellProfileImage.setImageBitmap(image); and move it above return cellView; don't forget to declare Bitmap image after View cellView = convertView; but you can also create a class viewHolder that contain your imageview like this link:http://lucasr.org/2012/04/05/performance-tips-for-androids-listview/

Categories

Resources