2

I am struggling to update a list view with data from a database, this works nicely by using a SimpleCursorAdapter. But the image view on the rows is not updated on the activity start, I have to scroll through the list a few times and only then the images are loaded in the image view.

This is the binder i am using for the SimpleCursorAdapter:

 private class PromotionViewBinder implements SimpleCursorAdapter.ViewBinder {
            private int done;
            public boolean setViewValue(View view, Cursor cursor, int index) {
                Log.e(""+cursor.getCount(),"");
                View tmpview = view;

                 if (index == cursor.getColumnIndex(PromotionsTable.SEEN_COL)) {
                     boolean read = cursor.getInt(index) > 0 ? true : false;
                     TextView title = (TextView) tmpview;
                     if (!read) {
                         title.setTypeface(Typeface.DEFAULT_BOLD, 0);
                     } else {
                         title.setTypeface(Typeface.DEFAULT);
                     }
                     return true;
                 }  else if (tmpview.getId() == R.id.promotions_list_row_image){
                        String imageURL = cursor.getString(index);
                        Log.e("",imageURL);
                        imageRetriever.displayImage(imageURL, (ImageView)tmpview);
                        return true;
                } else {
                     return false;
                 }
             }
         }

The image retriever class is the LazyList example from here. As you will see this is using a runnable to retrieve the images and once the task is done is automatically updating the given imageView...Do you think that the reference to the imageView is lost somewhere on the way?

Thanx in advance, Nick

package com.tipgain.promotions;

The image retriever class:

/**
 * This class is used for retrieving images from a given web link. it uses local
 * storage and memory to store the images. Once a image is downloaded
 * successfully the UI gets updated automatically.
 * 
 * 
 */
public class ImageRetriever {
    private final String TAG = ImageRetriever.class.getName();

    private MemoryImageCache memoryImgCache = new MemoryImageCache();
    private LocalStorageImageCache localFileCache;
    private Map<ImageView, String> imageViewHolders = Collections
            .synchronizedMap(new WeakHashMap<ImageView, String>());
    private ExecutorService execService;
    final int defaultImageID = R.drawable.photo_not_available;

    public ImageRetriever(Context context) {
        localFileCache = new LocalStorageImageCache(context);
        execService = Executors.newFixedThreadPool(5);
    }

    public void displayImage(String url, ImageView imageView) {
        imageViewHolders.put(imageView, url);
        Bitmap bmp = memoryImgCache.retrieve(url);

        if (bmp != null) {
            Log.e("case 1", " " + (bmp != null));
            imageView.setImageBitmap(bmp);

        } else {
            Log.e("case 2", " " + (bmp == null));
            addImageToQueue(url, imageView);
            imageView.setImageResource(defaultImageID);
        }
    }

    private void addImageToQueue(String url, ImageView imageView) {
        NextImageToLoad img = new NextImageToLoad(url, imageView);
        execService.submit(new ImagesRetriever(img));
    }

    /**
     * This method is used for retrieving the Bitmap Image.
     * 
     * @param url
     *            String representing the url pointing to the image.
     * @return Bitmap representing the image
     */
    private Bitmap getBitmap(String url) {
        File imageFile = localFileCache.getFile(url);

        // trying to get the bitmap from the local storage first
        Bitmap bmp = decodeImageFile(imageFile);
        if (bmp != null)
            return bmp;

        // if the file was not found locally we retrieve it from the web
        try {
            URL imageUrl = new URL(url);
            HttpURLConnection conn = (HttpURLConnection) imageUrl
                    .openConnection();
             conn.setConnectTimeout(30000);
             conn.setReadTimeout(30000);
            conn.setInstanceFollowRedirects(true);
            InputStream is = conn.getInputStream();
            OutputStream os = new FileOutputStream(imageFile);
            Utils.CopyStream(is, os);
            os.close();
            bmp = decodeImageFile(imageFile);
            return bmp;
        } catch (MalformedURLException e) {
            Log.e(TAG, e.getMessage());
        } catch (FileNotFoundException e) {
            Log.e(TAG, e.getMessage());
        } catch (IOException e) {
            Log.e(TAG, e.getMessage());
        }
        return null;
    }

    /**
     * This method is used for decoding a given image file. Also, to reduce
     * memory, the image is also scaled.
     * 
     * @param imageFile
     * @return
     */
    private Bitmap decodeImageFile(File imageFile) {
        try {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(imageFile), null,
                    options);

            // Find the correct scale value. It should be the power of 2.
            // Deciding the perfect scaling value. (^2).
            final int REQUIRED_SIZE = 100;
            int tmpWidth = options.outWidth, tmpHeight = options.outHeight;
            int scale = 1;
            while (true) {
                if (tmpWidth / 2 < REQUIRED_SIZE
                        || tmpHeight / 2 < REQUIRED_SIZE)
                    break;
                tmpWidth /= 2;
                tmpHeight /= 2;
                scale *= 2;
            }

            // decoding using inSampleSize
            BitmapFactory.Options option2 = new BitmapFactory.Options();
            option2.inSampleSize = scale;
            return BitmapFactory.decodeStream(new FileInputStream(imageFile),
                    null, option2);
        } catch (FileNotFoundException e) {
            Log.e(TAG, e.getLocalizedMessage());
        }
        return null;
    }

    private boolean reusedImage(NextImageToLoad image) {
        Context c = image.imageView.getContext();
        c.getContentResolver().notifyChange(PromotionsProvider.CONTENT_URI, null);

        String tag = imageViewHolders.get(image.imageView);
        if ((tag == null) || (!tag.equals(image.url)))
            return true;
        return false;
    }

    /**
     * Clears the Memory and Local cache
     */
    public void clearCache() {
        memoryImgCache.clear();
        localFileCache.clear();
    }

    /**
     * This class implements a runnable that is used for updating the promotions
     * images on the UI
     * 
     * 
     */
    class UIupdater implements Runnable {
        Bitmap bmp;
        NextImageToLoad image;

        public UIupdater(Bitmap bmp, NextImageToLoad image) {
            this.bmp = bmp;
            this.image = image;
            Log.e("", "ui updater");
        }

        public void run() {
            Log.e("ui updater", "ui updater");
            if (reusedImage(image))
                return;
            Log.e("nick", "" + (bmp == null) + "     chberugv");
            if (bmp != null){
                image.imageView.setImageBitmap(bmp);
                Context c = image.imageView.getContext();
                c.getContentResolver().notifyChange(PromotionsProvider.CONTENT_URI, null);

            }else
                image.imageView.setImageResource(defaultImageID);

            }
    }

    private class ImagesRetriever implements Runnable {
        NextImageToLoad image;

        ImagesRetriever(NextImageToLoad image) {
            this.image = image;
        }

        public void run() {
            Log.e("images retirever", " images retriever");
            if (reusedImage(image))
                return;
            Bitmap bmp = getBitmap(image.url);
            memoryImgCache.insert(image.url, bmp);
            if (reusedImage(image))
                return;
            UIupdater uiUpdater = new UIupdater(bmp, image);
            Activity activity = (Activity) image.imageView.getContext();
            activity.runOnUiThread(uiUpdater);
            //Context c = image.imageView.getContext();
            //c.getContentResolver().notifyChange(PromotionsProvider.CONTENT_URI, null);


        }
    }

    /**
     * This class encapsulates the image being downloaded.
     * 
     * @author Nicolae Anca
     * 
     */
    private class NextImageToLoad {
        public String url;
        public ImageView imageView;

        public NextImageToLoad(String u, ImageView i) {
            url = u;
            imageView = i;
        }
    }

}

Modified Runnable:

class UIupdater implements Runnable {
    Bitmap bmp;
    NextImageToLoad image;

    public UIupdater(Bitmap bmp, NextImageToLoad image) {
        this.bmp = bmp;
        this.image = image;
    }

    public void run() {
        if (reusedImage(image))
            return;
        if (bmp != null){
            image.imageView.setImageBitmap(bmp);
            Context c = image.imageView.getContext();
            c.getContentResolver().notifyChange(PromotionsProvider.CONTENT_URI, null);

        }else
            image.imageView.setImageResource(defaultImageID);

        }
}
Community
  • 1
  • 1
Nick
  • 65
  • 1
  • 8

3 Answers3

1

Thats an interesting way to do what you are doing. Have you tried extending the Simple Cursor Adapter?

  1. What you do is implement a ViewHolder and put your imageview in it.

  2. Then in your ImageRetriever, write a Listener which will be called once the image is ready and retrieved.

  3. Implement this listener in the Viewholder.

  4. You create the view in getView() and request for the image in BindView().

  5. Once the image gets loaded, the list will be refreshed automatically.

Shubhayu
  • 13,402
  • 5
  • 33
  • 30
  • I found an easier way to do i but I am not sure if it is costing too much cpu or not.....in any of the runnables i have, i called Context c = image.imageView.getContext(); c.getContentResolver().notifyChange(PromotionsProvider.CONTENT_URI, null); and the list gets updated?...do you think that this is a good solution? – Nick Mar 16 '12 at 10:18
  • i added the modified runnable class in the first post, so you can see better what I have done...many thanks – Nick Mar 16 '12 at 10:22
  • This implementation gets more and more interesting. Its somethign new for me. :) As far as CPU consumption is concerned, I would rather not comment. I'll spend sometime in understanding this properly. Are you seeing any sort of performance issues? – Shubhayu Mar 16 '12 at 10:30
  • the phone i am using to test this has a 1.5 ghz cpu, so I don't see any noticeable cpu drain....I am only asking because the images are loading each time the user scrolls the screen. It look in the soft cache first, then on the local storage cache(sdcard) and if not found another thread is launched and downloads the image and saves it on the sdcard.... – Nick Mar 16 '12 at 16:50
  • ya, thats the usual way the imagedownloader works. And is the most efficient i guess, i have used a similar downloader in a app and it seems to be working fine :) So I think u r good to go! – Shubhayu Mar 16 '12 at 17:00
  • the only bug I found is that when an image is not found on the sd card or the url is wrong it gets in an infinite loop for some reason.....the threads are continuously called and i can't get to the bottom of this issue:))... – Nick Mar 18 '12 at 15:49
0

one way to do it is by calling notifyDataSetChenged on listview, and another was is to have adapter as member variable and when something changes on listview you call a function that assigns new listadapter to member adapter. That way your list will be redraw on change.

Mayank
  • 8,777
  • 4
  • 35
  • 60
  • but where should I call notifiydatasetChange?..in the setViewValue method? – Nick Mar 16 '12 at 00:22
  • anytime you see that data is changed or UI needed to be updated as your database is updated, you call notifyDatasetChanget(), as this will call adapter and redraw views. You can also override notifyDatasetchanged function for more complicated function or as required – Mayank Mar 16 '12 at 02:50
0

I guess, you have to use some handler, calling after image load, which will call notifyDataSetChanged for list adapter

Roman Truba
  • 4,401
  • 3
  • 35
  • 60
  • can you post some example on how to do that?...i tried loads of different ways but it just doesn't seem to work...the weird thing is that after I scroll a few times all the images are loaded properly:D – Nick Mar 16 '12 at 01:01
  • Sorry, I can't, but I think you need to make couple changes in imageRetriever for your case. Maybe you can post this complete part of your project somewhere – Roman Truba Mar 16 '12 at 01:10
  • ideated the first post with the ImageRetriever class... your help is appreciated!..thanks – Nick Mar 16 '12 at 01:48