ListView displaying image on rows that shouldn't have one - android

I have a listView that is implemented with a custom adapter I created, which includes an imageView. Throughout the list each item may or may not have an image attached(It's not necessary).
To load the image into the imageView I use Picasso library, in the getView method.
When it comes to rows that do have an image associated, my code works fine.
The problem is when the list is being displayed, rows that should not have an image are displaying one.
Here is my getView() method:
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
ReportHolder holder = null;
if(convertView==null){
LayoutInflater inflater=((Activity)context).getLayoutInflater();
convertView = inflater.inflate(layoutResourceId,null);
holder = new ReportHolder();
holder.imageReportView=(ImageView)convertView.findViewById(R.id.reportImage);
holder.reportLocation=(TextView) convertView.findViewById(R.id.report_location);
holder.reportDescription=(TextView) convertView.findViewById(R.id.report_description);
holder.reportStatus=(TextView) convertView.findViewById(R.id.report_status);
convertView.setTag(holder);
}
else
holder=(ReportHolder)convertView.getTag();
ReportData data = reportDataList.get(position);
holder.reportLocation.setText(data.address);
holder.reportDescription.setText(data.description);
holder.reportStatus.setText(data.status);
Picasso picasso = Picasso.with(this.context);
if(data.url!=null)
picasso.load("https://fourth-landing-159416.appspot.com/gcs/"+data.url+"_thumbnail").into(holder.imageReportView);
return convertView;
}
I know I'm fetching my information well because no information between rows repeats itself except the pictures. So what am I missing here?
FMI My adapter is created in a nested ASyncTask, because I´m required to fetch the information through an HTTP connection before I can insert it in the adapter:
#Override
protected void onPostExecute(final String result) {
mFeedTask = null;
mFeed.removeFooterView(mProgressView);
if(result!=null){
if(result.contains("HTTP error code: 403")){
Toast.makeText(mContext,"Token invalid. Please login again.", Toast.LENGTH_SHORT).show();
}
else if(result.equals("[]"))
Toast.makeText(mContext,"Nothing more to show.", Toast.LENGTH_SHORT).show();
else{
try {
Gson gson = new Gson();
JSONArray reports = new JSONArray(result);
LinkedList<ReportData> tmpList = new LinkedList<ReportData>();
for(int i=0; i < reports.length(); i++){
ReportData data = gson.fromJson(reports.getString(i), ReportData.class);
tmpList.add(data);
}
reportDataList.addAll(tmpList);
if(reportDataList.size()==tmpList.size()){
// First, we set the empty adapter and set up the item click listeners
adapter = new CustomAdapter(mContext,R.layout.custom_feed_row,reportDataList);
mFeed.setAdapter(adapter);
}
else {
updateTriggered=false;
adapter.notifyDataSetChanged();
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
And as I have a relatively large amount of information to transfer I have to call this task multiple times. On the first time called the adapter is created and from then on notifyDataSetChanged is called to update it.
Much Help Appreciated!
I thank you in advance!
Cheers

Because listview recycling same views, so you should remove the image if this row shouldn't contain image
Do something like this in your getView:
if(data.url!=null)
picasso.load("https://fourth-landing-159416.appspot.com/gcs/"+data.url+"_thumbnail").into(holder.imageReportView);
else {
holder.imageReportView.setImageDrawable(null);;
}

Related

Why are wrong images getting loaded in ListView occasionally?

I have a listview which loads images in every cell in async. When I try to scroll down slowly(after all the images in the current view are loaded), it works flawlessly.
But when I try to scroll down before they are even loaded and scroll up, I face this issue. The cells start to show up images which don't correspond to them.
My getView method looks like this:
public View getView(final int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
View rowView = null;
if(convertView == null) {
rowView = inflater.inflate(R.layout.list_posts_item, null);
final Holder holder=new Holder();
holder.tvTitle=(TextView) rowView.findViewById(R.id.tvTitleNamePost);
holder.ivPrimaryImage=(ImageView) rowView.findViewById(R.id.ivPrimaryImage);
holder.tvLocality=(TextView) rowView.findViewById(R.id.tvLocalityPosts);
holder.tvDateCreated=(TextView) rowView.findViewById(R.id.tvDateCreated);
rowView.setTag(holder);
}else {
rowView=convertView;
}
Holder holder = (Holder)rowView.getTag();
holder.ivPrimaryImage.setId(position);
holder.ivPrimaryImage.setTag(listOfPosts.get(position).getPostId());
holder.ivPrimaryImage.setImageBitmap(null); // Added for flickering issue
holder.tvTitle.setText(listOfPosts.get(position).getTitle());
holder.tvLocality.setText(listOfPosts.get(position).getLocality());
holder.tvDateCreated.setText(listOfPosts.get(position).getCreatedDate());
postId = listOfPosts.get(position).getPostId();
Image image = new Image();
image.setImg(holder.ivPrimaryImage);
if (!"N".equalsIgnoreCase(listOfPosts.get(position).getHasImage()) ) {
if(!tagsCaching.containsKey(postId))
new GetPrimaryImages().execute(image);
else
holder.ivPrimaryImage.setImageBitmap(tagsCaching.get(postId));
}
return rowView;
}
And my Async call class looks like this:
public class GetPrimaryImages extends AsyncTask<Image, Void, Bitmap> {
ImageView imageView = null;
protected Bitmap doInBackground(Image... images) {
this.imageView=images[0].getImg();
// Building Parameters
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("postid",(String)(this.imageView.getTag()) ));
json = jsonParser.makeHttpRequest(CommonResources.getURL("get_primary_image"),
"POST", params);
if(json == null){
return null;
}
Log.d("Fetching Image",imageView.getTag()+ json.toString());
tagsDownloaded.add((String)imageView.getTag());
// check for success tag
String TAG_SUCCESS = "success";
try {
int success = json.getInt(TAG_SUCCESS);
if (success == 0) {
image = json.getString("primaryimage");
}
} catch (JSONException e) {
e.printStackTrace();
}
return getImage(image);
}
/**
* After completing background task Dismiss the progress dialog
* **/
protected void onPostExecute(Bitmap result) {
tagsCaching.put((String)imageView.getTag(), result);
imageView.setImageBitmap(result);
}
public Bitmap getImage(String imageString) {
if("null".equalsIgnoreCase(imageString)){
return null;
}else{
byte[] decodedString = Base64.decode(imageString, Base64.DEFAULT);
Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
//image.setImageBitmap(decodedByte);
return decodedByte;
}
}
}
Edit:
I added a new instance variable to Holder:
public class Holder
{
TextView tvTitle;
ImageView ivPrimaryImage;
TextView tvLocality;
TextView tvDateCreated;
int position;
}
Set the same in the getView:
holder.position = position;
And passed the holder object to the Async task:
new GetPrimaryImages(position, holder).execute(image);
And modified the Async call class as follows:
1. Added cancel to the http call
2. Changed the onPostExecute method
public class GetPrimaryImages extends AsyncTask<Image, Void, Bitmap> {
int mPosition;
Holder mHolder;
public GetPrimaryImages(int position, Holder holder){
mPosition = position;
mHolder = holder;
}
ImageView imageView = null;
protected Bitmap doInBackground(Image... images) {
this.imageView=images[0].getImg();
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("postid",(String)(this.imageView.getTag()) ));
JSONObject json;
if(mHolder.position == mPosition)
json = jsonParser.makeHttpRequest(CommonResources.getURL("get_primary_image"),
"POST", params);
else {
json = null;
cancel(true);
}
// check log cat fro response
if(json == null){
return null;
}
Log.d("Fetching Image",imageView.getTag()+ json.toString());
tagsDownloaded.add((String)imageView.getTag());
// check for success tag
String TAG_SUCCESS = "success";
try {
int success = json.getInt(TAG_SUCCESS);
if (success == 0) {
image = json.getString("primaryimage");
}
} catch (JSONException e) {
e.printStackTrace();
}
return getImage(image);
}
protected void onPostExecute(Bitmap result) {
if (mHolder.position == mPosition) {
tagsCaching.put((String) imageView.getTag(), result);
imageView.setImageBitmap(result);
}
}
public Bitmap getImage(String imageString) {
//needs to wait
if("null".equalsIgnoreCase(imageString)){
return null;
}else{
byte[] decodedString = Base64.decode(imageString, Base64.DEFAULT);
Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
//image.setImageBitmap(decodedByte);
return decodedByte;
}
}
}
It seems to be working. :)
Now my doubt is what would be the best way to cache the images? Should be writing it to a file? and reading it from it every time I scroll up?
The problem is, while your async task ends its background operation, the element it was linked to has been recycled to hold another element of your collection.
Let's focus on elements position, and let's say your listview can display up to 4 elements.
The first time the listview calls getview for the first 4 elements, and four asynctasks are created and run.
Then you scroll to shouw positions 11 - 15, and the first element (the one related to position 1) gets recycled for position 11 before the asynctask ends.
Then the asynctask ends, and what you see is the image related to post 11 with the bitmap related to post 1.
A way to avoid this is knowing in the asynctask that the view was recycled, as suggested in this old post from Lucas Rocha.
Performance tips with listview
Check the post for insights on how listview works too:
Main "problem" is with ListViews implementation of reusing views and serial providing of AsyncTasks.
1) In ListView's adapter you correctly implement reusing of items. In ListView there are rendered only few items (items visible on screen + few top and down). If you start scrolling items which went out of screen are destroyed and theirs views are passed asi parameter to public View getView(final int position, View convertView, ViewGroup parent) as convertView.
This is first problem. List is reusing items.
2) Second thing is that AsyncTask is performed on another thread but more instances of asyncTask are performed on the same thread. it means they are performed serially. If you scroll quickly enough then you can make more requests on loading images with AsyncTask. At one time, there can be only few (i think 5) AsyncTask's jobs waiting in queue. Another ariving is ignored. So if you swipe quick enough you get this queue of 5 full and another image requests are ignored.
And this is It. You scroll, you start loading few images and the new displayed images are ignored. When you stop after while all AsyncTasks end and you got some "random" (previosly displaying) image loaded in your list item.
Sollution
This thing was discussed manny times and is solved. It is enough to use for example Picaso library:
https://futurestud.io/blog/picasso-adapter-use-for-listview-gridview-etc/
To add the answers, I would implement an image cache (e.g. as an URL-to-WeakReference-to-image hashmap). The getView() would access that cache and, if the image is not there, leave a request. When the image is loaded, the cache would examine the request list and notify the views that posted the requests (passing them both URL and the image). The views would compare the URL passed in notification to their current URL and either use the image or ignore it (if the view went out of screen or was reused, the image must be ignored).
Why request list. It is possible that multiple views manage to request some image and get reused before the image is loaded (especially if you scroll the list up and down several times).

Load Bitmaps/images in ListView Adapter

I'm trying to add images in a ListView which has an ArrayAdapter. Fyi, the toList() is a conversion from iterator to a list of the given DBObject.
I override the View getView() and set a textview and an image.
private static class EventAdapter extends ArrayAdapter<DBObject> {
public EventAdapter(Context context, int resource, Iterable<DBObject> events) {
super(context, resource, toList(events));
}
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
LayoutInflater vi = LayoutInflater.from(getContext());
v = vi.inflate(R.layout.adapter_event_list, null);
DBObject event = getItem(position);
if (event != null) {
//Get the logo if any
if( ((DBObject)event.get("events")).containsField("logo") ){
String logoURL = ((DBObject)((DBObject)event.get("events")).get("logo")).get("0").toString();
ImageView eventLogo = (ImageView) v.findViewById(R.id.eventLogoList);
new setLogo().execute(logoURL, eventLogo);
}
TextView title= (TextView) v.findViewById(R.id.eventTitleList);
title.setText( ((DBObject)event.get("events")).get("title").toString() );
}
return v;
}
protected static <T> List<T> toList( Iterable<T> objects ) {
final ArrayList<T> list = new ArrayList<T>();
for( T t : objects ) list.add(t);
return list;
}
//setLogo() method here. See below
}
The text in the textview is fine. However the images are getting messed up. They seem to load in wrong places in the list. The route of the code is: 1)Get from the DB (async) 2)populate the ListView 3) while populating load each image(second async).
Here is the setLogo() AsyncTask which is inside the EventAdapter above:
private class setLogo extends AsyncTask<Object,Void,Bitmap>{
ImageView eventLogo = null;
#Override
protected Bitmap doInBackground(Object...params) {
try{
Bitmap eventImage = downloadBitmap((String) params[0]);
eventLogo = (ImageView) params[1];
return eventImage;
}
catch(Exception e){
e.printStackTrace();
return null;
}
}
#Override
protected void onPostExecute(Bitmap eventImage) {
if(eventImage!=null && eventLogo!=null){
eventLogo.setImageBitmap(eventImage);
}
}
}
I did so (using an Async) which I believe is the correct way to load images from urls. I saw this post on multithreading and from which I borrowed the downloadBitmap() method.
As explained above the images are loaded in wrong places of the ListView. What can be a robust way to load them?
Also the idea to pass the v.findViewById(R.id.eventLogoList) inside the AsyncTask is that the program will distinguish each adapter's ImageView but it seems it doesn't.
Update
After following the problem that is causing this mix I found this SO question.
I altered my code in order to check if the if is causing the problem.
//Get the logo if any
if( ((DBObject)event.get("events")).containsField("logo") ){
String logoURL = ((DBObject)((DBObject)event.get("events")).get("logo")).get("0").toString();
ImageView eventLogo = (ImageView) row.findViewById(R.id.eventLogoList);
//new setLogo().execute(logoURL, eventLogo);
TextView title= (TextView) row.findViewById(R.id.eventTitleList);
title.setText( "Shit happens" );
}
Let's say I have 40 items. The Shit happens is set on the fields that a logo field exists. If I scroll down/up the order changes and the text gets messed up. It is because the stack created inside the loop is small than the maximum of the list..I guess... I am still struggling.
PS: I found this easy library to load images asynchronously instead of DYI stuff.
Update 2
I added an else with a static url. Because of the time it take to the image to load they are still misplaced.
I would really go for a good library like Picasso.
It will handle all the hard part for you and it's very well written.
http://square.github.io/picasso/

Android delete listview row in custom array adapter

I have a listview that for displaying detail data. I'm storing my data in an ArrayList of Strings. However, some of the fields may not have any data to display, but I need to keep the array length the same to match a static titles array. I can trap the empty data in my getView method in my custom base adaptor here:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.drug_detail_cell, parent, false);
}
// check array bounds
if (position < getCount()) {
// check for null items
String item = items.get(position);
if (item != null) {
// get the text views
TextView title = (TextView) convertView.findViewById(R.id.item_title);
TextView sub = (TextView) convertView.findViewById(R.id.item_subtitle);
title.setText(titles.get(position));
sub.setText(item.toString());
} else {
// delete row
}
}
return convertView;
}
My problem is that while the data does not display, I still have an empty row in my listview. My question is how do I delete that row? Any help would be greatly appreciated. Thanks in advanced.
For removing a row from the CustomListAdapter:
Remove the item from the ArrayAdapter from the specified index, after that call notifyDatasetChanged . It will update your listView.
In CustomAdapterClass:
#Override
public void remove(String object) {
super.remove(object);
// your other code
}
In ListActivity class:
CustomAdapterClass adap = new CustomAdapterClass();
adap.remove("hello world");
adap.notifyDatasetChanged(); // this will update your listView
My code is a bare bone example to depict how to achieve your goal.
I have a tip: in else clause you return a empty view
else{
View v = new View(context);
v.setLayoutParams(new AbsListView.LayoutParams(0, 0));
return v;
}
But if your list have divider, the divider below the empty view will be double.
In a different: I think you should handle all null data before getView call. I mean:
- In getCount(){
loop and create a new map from position and not null data
loop and count all data!=null; return count;
}
use new map in getView function.
Hope this help.

Android listview duplicates the item on scroll

I have a listview with one textview and a button in each row. Button background is setting using some conditions. If i have number of rows in list and scrolled, the button background get shuffling. That means on scrolling wrong background is setting as button background. How can i resolve this issue? My adapter class is as shown below:
public View getView(int position, View convertView, ViewGroup parent) {
final PhotoCondtionthreeItemView item;
Photo place = (Photo) getItem(position);
if (convertView != null) {
item = (PhotoCondtionthreeItemView) convertView;
} else {
item = new PhotoCondtionthreeItemView(context, place,
membershipSign);
}
item.setShareTag(place.getLink()+"###"+place.getServerPhotoId());
item.setBaseLayoutTag(place.getLink() + "###"
+ place.getServerPhotoId() + "###" + place.getIsGallery()
+ "###" + place.getId());
File file;
try {
file = new File(place.getLink());
if (file.exists()) {
if (cache.containsKey(place.getLink())) {
item.setThumbImg(cache.get(place.getLink()));
} else {
Bitmap bitmap = BitmapResizer.decodeFile(file, 50, 50);
item.setThumbImg(bitmap);
cache.put(place.getLink(), bitmap);
}
} else {
item.setThumbImgMissing();
}
} catch (Exception e) {
Log.e("PhotoCondThree ERROR", e.toString());
}
return item;
}
In PhotoCondtionthreeItemView class created the row for list. (not used xml for list row).
can anyone help me?
Thank you
call yourListViewadapter.notifyDataSetChanged() when data is changed
That sounds like an issue which is caused by the ListView re-using layout elements. Without seeing the code that is actually setting the image to the view it's a little hard to say for sure but maybe this blog post will help...
http://android-developers.blogspot.com/2009/05/drawable-mutations.html

Android: JSONArray & JSONObject to ListView strange readings

Tried using the following:
Populate Listview from JSON
To make a listview which uses a JsonArray containing Json Objects. For some reason, the
'public View getView(int position, View convertView, ViewGroup parent)'
code is fired more times than there are contents in the jsonarray.
I made a control test to check up on this and I found that even with just 1 Jsonobject within the jsonarray, I came up with 32 times the getView code was activated.
I am rather confused as to why this is happening, as my friends have managed to make similar codes to mine, but without the huge number of activations I am suffering from. Am I being rather slow, and this is because the individual Jsonobject has, not only the image and text in them, but about 15 other items within it? Or is ther another cause?
I would appreciate any aid towards this, I am posting the adapter code below:
public class ArticleAdapter extends BaseAdapter{
private JSONArray items;
private Context cont;
public ArticleAdapter(Context context, JSONArray array)
{
super();
this.items = array;
this.cont = context;
}
#Override
public int getCount() {
return items.length();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View v = convertView;
WebIView sath;
TextView sati;
Log.i("Seiji", "Checking! " + position);
try
{
if(!items.isNull(position))
{
JSONObject item = items.getJSONObject(position);
if (v == null) {
v = LayoutInflater.from(cont).inflate(R.layout.saved_articles_listitem, null);
}
sath = (WebIView) v.findViewById(R.id.sathumbnail);
sati = (TextView) v.findViewById(R.id.satitle);
if(item.has("image") && sath != null)
{
JSONObject thisImage = item.getJSONObject("image");
sath.reset();
sath.setImageUrl(thisImage.getString("thumbnail"));
sath.loadImage();
}
if(sati != null)
{
sati.setText(item.getString("title"));
}
}else{
return null;
}
}
catch(Exception e)
{
Log.e("num", "Saved Art Error! " + e.toString());
}
return v;
}
}
the code which activates this class is the following:
ListView savedArtList = (ListView) sav.findViewById(R.id.savelist);
ArticleAdapter savedadapter = new ArticleAdapter(cont, flip);
ArtList.setAdapter(savedadapter);
EDIT:
Thanks to some very helpful advice I was able to figure out what was going wrong. The Listview was resizing itself every time a new row was added because I had set the views height to be 'wrap_content'. I hadnt realised that this would cause problems, but once I had set it to 'fill_parent' (or a set value in other cases), the issue disappeared and I didnt have this problem any more.
Thank you againfor the helpful advice!
getView will be called many times - per visible cell when the list view is being laid out, per visible cell when the list view is being drawn + more. This is normal behaviour and getView should be efficient. Its possible your images and/or text are making the height of each cell change as they're loaded in, meaning other cells may become visible / go off screen etc.

Categories

Resources