Why are wrong images getting loaded in ListView occasionally? - android

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).

Related

ListView displaying image on rows that shouldn't have one

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);;
}

ListView shows wrong and duplicates images

I have a ListView and 12 ImageViews in it.
Every ImageView has different image which is loading from url. Images are shuffled and sometimes duplicated either I scroll or not.
I tried 10 other ways to solve this problem but have not succeeded.
This is the code I download and show images:
private static class ViewHolder {
ImageView imageViewPhoto;
Bitmap photo;
boolean isDownloading;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder viewHolder;
if (convertView == null) {
// ...classical view holder and other operations...
if (!viewHolder.isDownloading) {
viewHolder.isDownloading = true;
IImageDownload downloadInterface = new IImageDownload() {
#Override
public void onError(VolleyError error, String url) {
}
#Override
public void onDownloaded(Bitmap response, String url) {
viewHolder.photo = response;
notifyDataSetChanged();
}
};
imageDownloader.downloadImage(dataList.get(position).getPhotoPath(), true, downloadInterface);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
if (viewHolder.photo != null) {
viewHolder.imageViewPhoto.setImageBitmap(viewHolder.photo);
} else {
viewHolder.imageViewPhoto.setImageResource(R.drawable.gray_background);
}
}
Thanks in advance for any ideas!
Before:
imageDownloader.downloadImage(dataList.get(position).getPhotoPath(), true, downloadInterface);
Put:
viewHolder.photo.setImageBitmap(null);
This will reset the ImageView's bitmap, as it is being recycled and therefore keeping its image.
You should have something like this:
if (!viewHolder.isDownloading) {
// download the image in a worker thread
} else {
// cancel the current downloading and start a new one with the new url
}
Since ListView items are reusable. Your items are starting the image downloads, but when you start scrolling, those same items could still be downloading the images when they are already being reused. So when the worker thread has finished, the bitmaps are set in the wrong place and even worse, you never started the downloads for those reused items because the viewholder.isDownloading said it was already downloading an image.
A) You only initiate the download when the convertView is instantiated. You are recycling the rows so you may have a data set larger than the number of row Views that you actually use. This is not the right place to begin downloading an image. You want to do this per viewed position, not per View instantiated.
B) When you fire off a background task to download the image it may return later (after fetching) and replace a row with the wrong image as the row may now represent the wrong position (given row recycling).
Asynchronous image loading in a recycling ListView is slightly more complicated than it first seems. As the user scrolls through the list, you'll need to fire off downloads when a position is viewed, and cancel calls that are now redundant (as they are for a previously visible position).
You may wish to read more on view recycling in a ListView to get a better understanding of what is happening.
Also consider using an image downloading/caching library that handles these complexities such as Picasso.
Use UniversalImageLoader library to load images..
Try this
ImageLoader.getInstance().displayImage(url, holder.imgView, options);
to load images inside adapter..
Use DisplayImageOptions as follows inside constructor of adapter
options = new DisplayImageOptions.Builder()
.showImageOnLoading(android.R.color.transparent)
.showImageForEmptyUri(android.R.color.transparent)
.showImageOnFail(android.R.color.transparent)
.cacheInMemory(true)
.cacheOnDisk(true)
.considerExifParams(true)
.bitmapConfig(Bitmap.Config.RGB_565)
.build();
and add
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
inside onCreateView/onCreate of fragment/activity contaning the list

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/

Why thumbnail images are set automatically in the ListView

I have a ListView with thumbnail images. All visible rows in the ListView don't have problem.
But for those new rows below the visible ones, even though I tried not to assign any images to those thumbnail ImageViews, images started from the first row are copied exactly the same order as in visible rows. I set breakpoints at those lines of codes assigning the images at thumbnail ImageViews, no breakpoints are hit but still get the images. What is the theory behind? And how can I stop assigning images automatically at the rows below the visible ones.
Thanks
EDIT1:
public View getView(int position, View convertView, ViewGroup parent) {
View vi=convertView;
ViewHolder viewHolder=new ViewHolder();
LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if(vi==null){
vi = inflater.inflate(R.layout.list_row, parent, false);
viewHolder.id=(TextView)vi.findViewById(R.id.title);
viewHolder.thumbnailImage=(ImageView)vi.findViewById(R.id.list_image);
viewHolder.activationStatus = (TextView)vi.findViewById(R.id.activated);
//lazy load image
BitmapWorkerTask task = new BitmapWorkerTask(viewHolder.thumbnailImage);
//if beyond visible rows, position
//becomes zero again, at that time cnt is not zero
//so task is not executed, to prevent image assignment
//for rows below the visible ones
if(position == cnt){
String id = listIDs.get(position);
task.execute(id);
cnt++;
}else{
cnt = 0;
}
//Lazy image update
class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image in background.
#Override
protected Bitmap doInBackground(String... params) {
Bitmap bitmap = null;
dbHelper.open();
byte[] img_bytes = dbHelper.getImagebyIDnumber(params[0]);
bitmap = BitmapFactory.decodeByteArray(img_bytes, 0, img_bytes.length);
dbHelper.close();
return bitmap;
}
// Once complete, see if ImageView is still around and set bitmap.
#Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
Check your Layout. Perhaps you settet android:src = "#drawable..." by default for your images.
Configure your listView data in your adaper.
EDITED 22.08.
try to use this:
if(vi==null){
...
//findView by ID here;
...
vi.setTag(viewHolder);
} else {
viewHolder = ( ViewHolder ) vi.getTag();
}
//to do what you want here;
//setting values etc.
You can find good explanation 'how to work with ViewHolder' here ViewHolder Pattern
This is because ListView reuses views getting invisible for visible views. If you want to hide images, you need to do this explicitly in getView() method of your adapter.

Listview in android did not refresh the view until dragged

I am using the code below:
private Runnable returnRes = new Runnable() {
#Override
public void run() {
if(m_orders != null && m_orders.size() > 0){
m_adapter.notifyDataSetChanged();
for(int i=0;i<m_orders.size();i++)
m_adapter.add(m_orders.get(i));
}
m_ProgressDialog.dismiss();
m_adapter.notifyDataSetChanged();
}
};
but the weird thing is, after the list populates, the only item available is the first thing on the list the rows directly below would be empty unless I drag down out of view then back again then it'd show. I'm pretty sure the code above is right as I followed a tutorial. But, I cant expect the user to drag down and back again to see the things involved...
And to add, I just noticed that my datas are not populated properly as this warning would appear 07-19 23:54:49.947: WARN/InputManagerService(58): Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy#44eb97c0
and I'm quite sure that my codes are correct and the following is where it stops:
public View getView(int position, View convertView, ViewGroup parent){
View v = convertView;
if(v != null){
return v;
}
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.row, null);
Log.d("added", "g" + position);
Grade g = grades.get(position);
if(g != null){
TextView name = (TextView) findViewById(R.id.bottomtext);
TextView id = (TextView) findViewById(R.id.toptext);
if(name != null)
name.setText(g.getName());
if(id != null)
id.setText(g.getId());
Log.d("grade", "grade " + g.toString());
}
return v;
}
and from the LogCat trace I would only get to position 3 :( what could be the problem?
someone please help me...
LoginByHttpPost gradeIndex = new LoginByHttpPost();
HttpURLConnection gradePage = gradeIndex.doHttpGet(TARGETURL);
String gradeInd = gradeIndex.readResponse(gradePage);
Document doc = Jsoup.parse(gradeInd);
// do more things here
Log.d("grade now ", grades.get(0).text());
Log.d("gradef now ", gradesF.text());
for(int i = 0; i < grades.size(); i += 5){
Grade grade = new Grade();
grade.setId(grades.get(i).text());
grade.setName(grades.get(i + 1).text());
//gradeList.add(grade);
ga.add(grade); //this is my arrayadapter not sure where to add my object to through :(
}
for(int i = 0; i < gradesF.size(); i++){
gradeList.get(i).setGrade(gradesF.get(i).text());
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
Log.d("prob", e.getMessage());
}
this is called from the asyncatask in the function doInBackground()
Try calling ListView.invalidateViews() on the list view. Worked for me.
Even if you call notifyDataSetChanged() and/or notifyDataSetInvalidated() from the UI thread on the adapter, these only invalidates the data and not the views. Hence.
You should call notifyDataSetChanged() in the UI thread try using runOnUiThread().
The second thing is notifyDataSetChanged() should be called only after add, remove and clear functions.
You could try refreshing the listview by calling listView1.requestLayout() or listView1.setAdapter(adapter). You could also try adapter.notifyDataSetChanged(). If scrolling on listview makes the views visible, you could also try scrolling the listview to the bottom and then scroll back to the original position programmatically.
UPDATE:
I think the problem may be coming from your getView() function. Try changing it to this:
public View getView(int position, View convertView, ViewGroup parent)
{
View v = convertView;
if (v == null)
{
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.row, null);
Log.d("added", "g" + position);
}
Grade g = grades.get(position);
if(g != null)
{
TextView name = (TextView) findViewById(R.id.bottomtext);
TextView id = (TextView) findViewById(R.id.toptext);
if(name != null)
{
name.setText(g.getName());
}
if(id != null)
{
id.setText(g.getId());
}
Log.d("grade", "grade " + g.toString());
}
return v;
}
Ok, I solved the problem.
There is nothing wrong with the ListAdapter. The problem is from the parent views of the ListView.
onMeasure must be called on the ListView every time the layout is changed. i.e. onMeasure or onLayout is called on one of its parents.
I had a custom view as the parent of the parent of the ListView. In which I precisely refused to measure the children to make the layout process faster.
You want to do something in background then send some change to UI, right? If you are doing this, you should use AsyncTask, a simpler and more effective way for background processing. Whenever your want to change the UI, just call onProgressUpdate() then do what you want there.
I had a similar problem. A simple file manager: if I have an image I've to resize it with a separate thread. So I show a placeholder until the resized image is ready. After that I've to call notifyDataSetChanged() on the adapter. My solution is to use an handler like this on the adapter
public final Handler fileArrayAdapterHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
notifyDataSetChanged();
}
};
On the thread I send an empty message to the handler at the end of it....
With different message you could do many other things ...
i was having the same issue, what i was missing was that the position was not always been sent, for example was skipping (position 0 and 2) and these were no updating until i scrolled.
This fix it for me (See that i used an asynctask) went from this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
...
new AsyncTask<ViewHolder, Void, Bitmap>() {
private ViewHolder v;
#Override
protected Bitmap doInBackground(ViewHolder... params) {
// Code
}
#Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
if (v.position == position) {
// Code
}
}
}.execute(viewHolder);
return convertView;
}
To this (Created an inner class, pass the position in the constructor):
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
....
new DownloadImage(position).execute(viewHolder);
return convertView;
}
private class DownloadImage extends AsyncTask<ViewHolder, Void, Bitmap> {
private ViewHolder v;
private int myPosition;
public DownloadImage(int p) {
myPosition = p;
}
#Override
protected Bitmap doInBackground(ViewHolder... params) {
// Code
return result;
}
#Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
if (v.position == myPosition) {
// Code
}
}
}
As some others have already stated, this problem is caused by the fact that the code is not called on the UI thread since you are executing it from an AsyncTask. Since I cannot comment yet, here's another answer.
I was facing the same issue: I updated the data (which was held in a static context), but the listview did not update after calling notifyDataSetChanged() on the adapter. After dragging/scrolling, the UI is updated and the data automatically refreshed.
The issue is that only the original thread that created a view hierarchy can touch its views. I suppose you are running the Runnable from the callback Thus, you need to call it on the UI thread for the listview to update itself, in a fragment you would do something along the lines of:
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
// Change the data source here,
// eg. some ArrayList<ItemObject> in this case
someDataSource.clear();
someDataSource.add(new ItemObject());
// ...
// And notify the adapter of the changes to the data source
adapter.notifyDataSetChanged();
}
});
If you run adapter.notifyDataSetChanged() outside the UI thread, you will usually also run into a CalledFromWrongThreadException, some try catch block might have masked that.
Same as #ac19 's answer, problem was sloved by adding handler-message.
I use custom adapter and typical ListView, will update data if I get bluetooth callback. When I called "Adapter.notifyDataSetChanged()" in callback function, List didn't updated until I touched screen.
I defiend a message and add following code in callback function (replaced Adapter.notifyDataSetChanged())
Message m = new Message();
m.what = MessageYouDefined;
mHandler.sendMessage(m);
And added handler in onCreate
mHandler=new Handler(){
public void handleMessage(Message msg)
{
switch (msg.what){
case UpdateChargerList:
chargerAdapter.notifyDataSetChanged();
break;
}
super.handleMessage(msg);
}
};

Categories

Resources