Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Images are repeating in ListView

I have implemented android app which should download images from server and display them in ListView, but very interesting thing occures while images are downloading

As you can see in video pictures which haven't been downloaded yet are represented by those which have been already downloaded. How that can happen? I've thinking about it almost two days.

http://www.youtube.com/watch?v=lxY-HAuJO0o&feature=youtu.be

here is my code of ListView adapter.

public class MoviesAdapter extends ArrayAdapter<ParkCinema> {
        private ArrayList<ParkCinema> movieDataItems;   
        private Activity context;

        public MoviesAdapter(Activity context, int textViewResourceId, ArrayList<ParkCinema> movieDataItems) {
            super(context, textViewResourceId, movieDataItems);
            this.context = context;
            this.movieDataItems = movieDataItems;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) { 
            if (convertView == null) {
                LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                convertView = vi.inflate(R.layout.movie_data_row, null);
                }

            ParkCinema movie = movieDataItems.get(position);

            if (movie!=null){
                        ImageView imageView = (ImageView) convertView.findViewById(R.id.movie_thumb_icon);
                        String url = movie.poster();

                         if (url!=null) {
                            Bitmap bitmap = fetchBitmapFromCache(url);
                            if (bitmap==null) { 
                                new BitmapDownloaderTask(imageView).execute(url);
                            }
                            else {
                                imageView.setImageBitmap(bitmap);
                            } 
                        } 
            }
            return convertView;
        }

        private LinkedHashMap<String, Bitmap> bitmapCache = new LinkedHashMap<String, Bitmap>();

        private void addBitmapToCache(String url, Bitmap bitmap) {
            if (bitmap != null) {
                synchronized (bitmapCache) {
                    bitmapCache.put(url, bitmap);
                }
            }
        }

        private Bitmap fetchBitmapFromCache(String url) {

            synchronized (bitmapCache) {
                final Bitmap bitmap = bitmapCache.get(url);
                 if (bitmap != null) {
                    return bitmap;
                } 
            }

            return null;

        }


    private class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {

            private String url;
            private final WeakReference<ImageView> imageViewReference;

            public BitmapDownloaderTask(ImageView imageView) {
                imageViewReference = new WeakReference<ImageView>(imageView);
            }

            @Override
            protected Bitmap doInBackground (String... source) {
                url = source[0];
                Bitmap image;
                try{
                    image = BitmapFactory.decodeStream(new URL(url).openConnection().getInputStream());
                    return image;
                    }
                catch(Exception e){Log.e("Error", e.getMessage()); e.printStackTrace();}
                return null;
                } 


            @Override
            protected void onPostExecute(Bitmap bitmap) {       
                addBitmapToCache(url, bitmap);
                imageViewReference.get().setImageBitmap(bitmap);               
            }
        }
    }

Edit 3:

public View getView(int position, View convertView, ViewGroup parent) { 
    if (convertView == null) {
        LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
        convertView = vi.inflate(R.layout.movie_data_row, null);
        }
    ParkCinema movie = movieDataItems.get(position);
    ImageView imageView = (ImageView) convertView.findViewById(R.id.movie_thumb_icon);
    if (movie!=null){
                String url = movie.poster();

                    if (url != null) {
                        Bitmap bitmap = fetchBitmapFromCache(url);
                        if (bitmap == null) {
                            imageView.setImageResource(R.drawable.no_image);
                            new BitmapDownloaderTask(imageView).execute(url);
                        }
                        else {
                            imageView.setImageBitmap(bitmap);
                        }
                    }
                    else {
                        imageView.setImageResource(R.drawable.no_image);
                    }
                }
                else {
                    imageView.setImageResource(R.drawable.no_image);
                } 

    return convertView;

}
like image 870
Andrew Rahimov Avatar asked Mar 14 '13 15:03

Andrew Rahimov


1 Answers

Aha! I think I may know the issue. Right now, your getView method sets your ImageView like this:

  1. Gets movie object at position
  2. Pulls out the movie's thumbnail url
  3. Using that url, it tries to find the image in the cache
  4. If it finds the image, it sets it
  5. If it can't find the image, it starts an async network request to go get it, and sets it after it gets downloaded.

Your issus arises since ListView reuses its rows' Views. When the first View scrolls off the screen, rather than inflate a new one, ListView passes the now offscreen row's View in as convertView for you to reuse (this is for efficiency).

When your getView gets a convertView that is getting reused, its ImageView has already been set from the row that had it before, so you see the old image from the offscreen row's View. With your current getView process, you check for the new row's image, and it doesn't find it in the cache, it starts a request to download it. While it is downloading, you see the old image until you get the new image.

To fix this, you need to make sure you set every field in the row's View immediately, to make sure you don't have any Views showing stale data. I would suggest you set the ImageView to the default drawable resource (you have set in your R.layout.movie_data_row) while you wait for the network download to get the image.

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = vi.inflate(R.layout.movie_data_row, null);
    }

    ParkCinema movie = movieDataItems.get(position);
    ImageView imageView = (ImageView) convertView.findViewById(R.id.movie_thumb_icon);
    if (movie != null) {
        String url = movie.poster();

        if (url != null) {
            Bitmap bitmap = fetchBitmapFromCache(url);
            if (bitmap == null) {
                // Set the movie thumbnail to the default icon while we load
                // the real image
                imageView.setImageResource(R.drawable.movie_thumb_icon);
                new BitmapDownloaderTask(imageView).execute(url);
            }
            else {
                // Set the image to the bitmap we get from the cache
                imageView.setImageBitmap(bitmap);
            }
        }
        else {
            // Set the movie thumbnail to the default icon, since it doesn't
            // have a thumbnail URL
            imageView.setImageResource(R.drawable.movie_thumb_icon);
        }
    }
    else {
        // Set the movie thumbnail to the default icon, since there's no
        // movie data for this row
        imageView.setImageResource(R.drawable.movie_thumb_icon);
    }

-Edit-

Updated to be even more robust, using your drawable. You also have an issue with your BitmapDownloaderTask, it does not handle errors/null. Try adding this as well.

@Override
protected void onPostExecute(Bitmap bitmap) { 
    addBitmapToCache(url, bitmap);
    if (bitmap == null) {
        // Set the movie thumbnail to the default icon, since an error occurred while downloading
        imageViewReference.get().setImageResource(R.drawable.movie_thumb_icon);
    }
    else {
        imageViewReference.get().setImageBitmap(bitmap);
    }            
}
like image 195
Steven Byle Avatar answered Nov 13 '22 14:11

Steven Byle