7

In my application I need to download a lot of pictures from urls and display them in a gridView. (It can be between 1-200 pictures). I don't want to download all pictures at once. I read about lazy downloading and my question is: Can i get only one part of the Json, download the pictures in a different thread, and only if the user scroll down the gridView, I will continue to the other parts of the Json, and so on?

Edit: Hi again. I want to implement multi select in this gridView and i'm having difficulty to implement the code in the getView() method of the adapter. This is the example i'm using:example. How can I combine this code in my getView() method:

public View getView(int position, View convertView, ViewGroup parent) {
        CheckableLayout l;
        ImageView i;

        if (convertView == null) {
            i = new ImageView(Grid3.this);
            i.setScaleType(ImageView.ScaleType.FIT_CENTER);
            i.setLayoutParams(new ViewGroup.LayoutParams(50, 50));
            l = new CheckableLayout(Grid3.this);
            l.setLayoutParams(new GridView.LayoutParams(GridView.LayoutParams.WRAP_CONTENT,
                    GridView.LayoutParams.WRAP_CONTENT));
            l.addView(i);
        } else {
            l = (CheckableLayout) convertView;
            i = (ImageView) l.getChildAt(0);
        }

        ResolveInfo info = mApps.get(position);
        i.setImageDrawable(info.activityInfo.loadIcon(getPackageManager()));

        return l;
    }


public class CheckableLayout extends FrameLayout implements Checkable {
    private boolean mChecked;

    public CheckableLayout(Context context) {
        super(context);
    }

    public void setChecked(boolean checked) {
        mChecked = checked;
        setBackgroundDrawable(checked ?
                getResources().getDrawable(R.drawable.blue)
                : null);
    }

    public boolean isChecked() {
        return mChecked;
    }

    public void toggle() {
        setChecked(!mChecked);
    }

}

my getView() code:

public View getView(int position, View convertView, ViewGroup parent) {
    // TODO Auto-generated method stub
    ViewHolder holder;
    View vi = convertView;

    if(convertView == null) {
        vi = inflater.inflate(com.egedsoft.instaprint.R.layout.item_clickable, null);
        holder = new ViewHolder();
        holder.imgPhoto = (ImageView)vi.findViewById(com.egedsoft.instaprint.R.id.imageClickable);
        vi.setTag(holder);

    } else {
        holder = (ViewHolder) vi.getTag();
    }


    if (!arrayUrls.get(position).getThumbnailUrl().isEmpty()){
        imageLoader.DisplayImage(arrayUrls.get(position).getThumbnailUrl(), holder.imgPhoto);
    }


    return vi;
}
user1787773
  • 888
  • 1
  • 11
  • 19

4 Answers4

37

This is how I fetch multiple photos in my activity. You can use parts of it for fit your logic. I use this to fetch Facebook Images from an Album. So my needs are (I am assuming) different from your needs. But again, the logic may be of use to you.

Note: This will be lengthy. ;-)

These are the global declarations for use through the ACtivity:

// HOLD THE URL TO MAKE THE API CALL TO
private String URL;

// STORE THE PAGING URL
private String pagingURL;

// FLAG FOR CURRENT PAGE
int current_page = 1;

// BOOLEAN TO CHECK IF NEW FEEDS ARE LOADING
Boolean loadingMore = true;

Boolean stopLoadingData = false;

This is the code block that fetches the initial set of Images:

private class getPhotosData extends AsyncTask<Void, Void, Void> {

    @Override
    protected Void doInBackground(Void... arg0) {

        // CHANGE THE LOADING MORE STATUS TO PREVENT DUPLICATE CALLS FOR
        // MORE DATA WHILE LOADING A BATCH
        loadingMore = true;

        // SET THE INITIAL URL TO GET THE FIRST LOT OF ALBUMS
        URL = "https://graph.facebook.com/" + initialAlbumID
                + "/photos&access_token="
                + Utility.mFacebook.getAccessToken() + "?limit=10";

        try {

            HttpClient hc = new DefaultHttpClient();
            HttpGet get = new HttpGet(URL);
            HttpResponse rp = hc.execute(get);

            if (rp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                String queryAlbums = EntityUtils.toString(rp.getEntity());

                JSONObject JOTemp = new JSONObject(queryAlbums);

                JSONArray JAPhotos = JOTemp.getJSONArray("data");

                // IN MY CODE, I GET THE NEXT PAGE LINK HERE

                getPhotos photos;

                for (int i = 0; i < JAPhotos.length(); i++) {
                    JSONObject JOPhotos = JAPhotos.getJSONObject(i);
                    // Log.e("INDIVIDUAL ALBUMS", JOPhotos.toString());

                    if (JOPhotos.has("link")) {

                        photos = new getPhotos();

                        // GET THE ALBUM ID
                        if (JOPhotos.has("id")) {
                            photos.setPhotoID(JOPhotos.getString("id"));
                        } else {
                            photos.setPhotoID(null);
                        }

                        // GET THE ALBUM NAME
                        if (JOPhotos.has("name")) {
                            photos.setPhotoName(JOPhotos.getString("name"));
                        } else {
                            photos.setPhotoName(null);
                        }

                        // GET THE ALBUM COVER PHOTO
                        if (JOPhotos.has("picture")) {
                            photos.setPhotoPicture(JOPhotos
                                    .getString("picture"));
                        } else {
                            photos.setPhotoPicture(null);
                        }

                        // GET THE PHOTO'S SOURCE
                        if (JOPhotos.has("source")) {
                            photos.setPhotoSource(JOPhotos
                                    .getString("source"));
                        } else {
                            photos.setPhotoSource(null);
                        }

                        arrPhotos.add(photos);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    @Override
    protected void onPostExecute(Void result) {

        // SET THE ADAPTER TO THE GRIDVIEW
        gridOfPhotos.setAdapter(adapter);

        // CHANGE THE LOADING MORE STATUS
        loadingMore = false;
    }

}

This is to detect when the user has scrolled to the end and fetch new set of images:

// ONSCROLLLISTENER
gridOfPhotos.setOnScrollListener(new OnScrollListener() {

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {

    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
        int lastInScreen = firstVisibleItem + visibleItemCount;
        if ((lastInScreen == totalItemCount) && !(loadingMore)) {

            if (stopLoadingData == false) {
                // FETCH THE NEXT BATCH OF FEEDS
                new loadMorePhotos().execute();
            }

        }
    }
});

And finally, this is how I fetch the next set of images:

private class loadMorePhotos extends AsyncTask<Void, Void, Void> {

    @Override
    protected Void doInBackground(Void... arg0) {

        // SET LOADING MORE "TRUE"
        loadingMore = true;

        // INCREMENT CURRENT PAGE
        current_page += 1;

        // Next page request
        URL = pagingURL;

        try {

            HttpClient hc = new DefaultHttpClient();
            HttpGet get = new HttpGet(URL);
            HttpResponse rp = hc.execute(get);

            if (rp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                String queryAlbums = EntityUtils.toString(rp.getEntity());
                // Log.e("PAGED RESULT", queryAlbums);

                JSONObject JOTemp = new JSONObject(queryAlbums);

                JSONArray JAPhotos = JOTemp.getJSONArray("data");

                // IN MY CODE, I GET THE NEXT PAGE LINK HERE

                getPhotos photos;

                for (int i = 0; i < JAPhotos.length(); i++) {
                    JSONObject JOPhotos = JAPhotos.getJSONObject(i);
                    // Log.e("INDIVIDUAL ALBUMS", JOPhotos.toString());

                    if (JOPhotos.has("link")) {

                        photos = new getPhotos();

                        // GET THE ALBUM ID
                        if (JOPhotos.has("id")) {
                            photos.setPhotoID(JOPhotos.getString("id"));
                        } else {
                            photos.setPhotoID(null);
                        }

                        // GET THE ALBUM NAME
                        if (JOPhotos.has("name")) {
                            photos.setPhotoName(JOPhotos.getString("name"));
                        } else {
                            photos.setPhotoName(null);
                        }

                        // GET THE ALBUM COVER PHOTO
                        if (JOPhotos.has("picture")) {
                            photos.setPhotoPicture(JOPhotos
                                    .getString("picture"));
                        } else {
                            photos.setPhotoPicture(null);
                        }

                        // GET THE ALBUM'S PHOTO COUNT
                        if (JOPhotos.has("source")) {
                            photos.setPhotoSource(JOPhotos
                                    .getString("source"));
                        } else {
                            photos.setPhotoSource(null);
                        }

                        arrPhotos.add(photos);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    @Override
    protected void onPostExecute(Void result) {

        // get listview current position - used to maintain scroll position
        int currentPosition = gridOfPhotos.getFirstVisiblePosition();

        // APPEND NEW DATA TO THE ARRAYLIST AND SET THE ADAPTER TO THE
        // LISTVIEW
        adapter = new PhotosAdapter(Photos.this, arrPhotos);
        gridOfPhotos.setAdapter(adapter);

        // Setting new scroll position
        gridOfPhotos.setSelection(currentPosition + 1);

        // SET LOADINGMORE "FALSE" AFTER ADDING NEW FEEDS TO THE EXISTING
        // LIST
        loadingMore = false;
    }

}

And this is the helper class to SET and GET the data collected from the queries above:

public class getPhotos {

    String PhotoID;

    String PhotoName;

    String PhotoPicture;

    String PhotoSource;

    // SET THE PHOTO ID
    public void setPhotoID(String PhotoID)  {
        this.PhotoID = PhotoID;
    }

    // GET THE PHOTO ID
    public String getPhotoID()  {
        return PhotoID;
    }

    // SET THE PHOTO NAME
    public void setPhotoName(String PhotoName)  {
        this.PhotoName = PhotoName;
    }

    // GET THE PHOTO NAME
    public String getPhotoName()    {
        return PhotoName;
    }

    // SET THE PHOTO PICTURE
    public void setPhotoPicture(String PhotoPicture)    {
        this.PhotoPicture = PhotoPicture;
    }

    // GET THE PHOTO PICTURE
    public String getPhotoPicture() {
        return PhotoPicture;
    }

    // SET THE PHOTO SOURCE
    public void setPhotoSource(String PhotoSource)  {
        this.PhotoSource = PhotoSource;
    }

    // GET THE PHOTO SOURCE
    public String getPhotoSource()  {
        return PhotoSource;
    }
}

If you also want the adapter code, let me know. I use Fedor's Lazy Loading method in the adapter.

Phew. Hope any of this helps. If you have further question, feel free to ask. :-)

EDIT: Adapter code added:

public class PhotosAdapter extends BaseAdapter {

    private Activity activity;

    ArrayList<getPhotos> arrayPhotos;

    private static LayoutInflater inflater = null;
    ImageLoader imageLoader; 

    public PhotosAdapter(Activity a, ArrayList<getPhotos> arrPhotos) {

        activity = a;

        arrayPhotos = arrPhotos;

        inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        imageLoader = new ImageLoader(activity.getApplicationContext());
    }

    public int getCount() {
        return arrayPhotos.size();
    }

    public Object getItem(int position) {
        return arrayPhotos.get(position);
    }

    public long getItemId(int position) {
        return position;
    }

    public View getView(final int position, View convertView, ViewGroup parent) {

        ViewHolder holder;

        View vi = convertView;
        if(convertView == null) {
            vi = inflater.inflate(R.layout.photos_item, null);

            holder = new ViewHolder();

            holder.imgPhoto = (ImageView)vi.findViewById(R.id.grid_item_image);

            vi.setTag(holder);
          } else {
            holder = (ViewHolder) vi.getTag();
        }


        if (arrayPhotos.get(position).getPhotoPicture() != null){
            imageLoader.DisplayImage(arrayPhotos.get(position).getPhotoPicture(), holder.imgPhoto);
        }
        return vi;
    }

    static class ViewHolder {
        ImageView imgPhoto;

    }
}

EDIT: Added steps to show Progress while loading:

Add a ProgressBar to you XML where you have the GridView right below it. Play around with the weight if it causes any problems.

<LinearLayout
    android:id="@+id/linlaProgressBar"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="horizontal" >

    <ProgressBar
        style="@style/Spinner"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="2dp" />
</LinearLayout>

In your Java, declare the Linearlayout linlaProgressBar as Global and cast it in the onCreate() and set it's visibility as linlaProgressBar.setVisibility(View.GONE);

And in the onPreExecute() use it like this:

@Override
protected void onPreExecute() {
    // SHOW THE BOTTOM PROGRESS BAR (SPINNER) WHILE LOADING MORE PHOTOS
    linlaProgressBar.setVisibility(View.VISIBLE);
}

And finally add, this in the onPostExecute()

// HIDE THE BOTTOM PROGRESS BAR (SPINNER) AFTER LOADING MORE ALBUMS
linlaProgressBar.setVisibility(View.GONE);
Community
  • 1
  • 1
Siddharth Lele
  • 27,623
  • 15
  • 98
  • 151
  • It's exactly what I need! I will try it now. I appreciate if you can post the adapter code as well. Thank you! – user1787773 Nov 07 '12 at 08:40
  • Thank you so much! Its working great. Do you know if there is any possibility to add a little loading sign at the bottom of the grid? (for times loading is taking few moments) – user1787773 Nov 09 '12 at 22:22
  • Give me about an hour to get to my office. – Siddharth Lele Nov 10 '12 at 02:52
  • @user1787773: Check the edit. This will give you a Progress Bar (Spinner) at the bottom while loading the data. – Siddharth Lele Nov 10 '12 at 05:55
  • @user1787773: Glad to have helped. :-) – Siddharth Lele Nov 10 '12 at 14:12
  • @SiddharthLele : I'm trying to achieve something similar to this, except that currently i'm loading the images from the resource file and not an url. Could you please tell me how i can go about that. And, i'm also not sure where the getPhotosData class is called. – jasdmystery Feb 11 '13 at 04:59
  • @jasdmystery: Here is a good tutorial that explains how to create an Array of Images from your `res` folder: http://wptrafficanalyzer.in/blog/listing-images-in-gridview-using-simple-adapter-in-android/. The `getPhotos` is a pretty standard Setter / Getter class file. – Siddharth Lele Feb 11 '13 at 05:06
  • @SiddharthLele Thanks for that tutorial. I've already got that bit working. What I need I need is to load is 20 images at a time in grid view and at the end of the scroll , show the spinner and load another 20 more images and so forth. But the images need to be loaded from the res folder. I wanted also wanted to know whether getPhotosData class (which extends the AsyncTask) is called in the main activity. Thank you very much in advance. – jasdmystery Feb 11 '13 at 05:43
  • @jasdmystery: Oh my bad. I misread your earlier comment. Yes. Both the `getPhotosData` and the `loadMorePhotos` classes are in the Activity. The `loadMorePhotos` is called only when the user has scrolled to the bottom of the page which is taken care of in the `setOnScrollListener` defined in the `onCreate()`. – Siddharth Lele Feb 11 '13 at 05:53
  • Thanks a lot. Still working on on it. Hope I can get back in case of further clarifications. – jasdmystery Feb 11 '13 at 06:05
  • @SiddharthLele: Could you please post `ImageLoader` code too?? – Vikalp Patel Feb 13 '13 at 17:09
  • @vikalpPatel: the imageloader is the lazy loader library. You will find a link to that right above the adapter code. – Siddharth Lele Feb 14 '13 at 00:58
  • @SiddharthLele okay got `ImageLoader`. But when i try to run the code `https:\\graph.facebook.com\` returns me null data. Applying same querying on `Graph Api Explorer` returns me correct data.If u few secs then take a look in here http://stackoverflow.com/questions/14861003/facebook-server-returns-null-data-on-query-querying-the-same-on-graph-api-explo – Vikalp Patel Feb 14 '13 at 05:56
  • @VikalpPatel: Are you still facing a problem in that? There is a solution already market. – Siddharth Lele Feb 14 '13 at 06:29
  • @SiddharthLele yeah. Can you point me in right direction by redirecting to somewhere?? – Vikalp Patel Feb 14 '13 at 06:43
  • Many of this functions are depreciated in 4.2, Can anyone please update this code or does any one know any lib. that help load album from Facebook? – DPP Jun 22 '13 at 09:44
  • May I ask what "arrPhotos" is? is it an ArrayList defined somewhere in your activity? – Jack Dec 15 '14 at 15:22
  • @Jack: Correct. `arrPhotos` is globally defined as: `ArrayList arrPhotos` in which `getPhotos` is the POJO class used to SET and GET to handle the various data returned. I kept a few things out to keep the post as short as possible. Hope this helps. – Siddharth Lele Dec 16 '14 at 04:21
  • Thanks for the response :) also, are you calling "getPhotosData" in the onCreate on your activity? because I realized after finishing all the classes and code you provide it is not called anywhere. – Jack Dec 16 '14 at 11:34
  • @Jack: That is correct too ;-). `getPhotosData`is indeed called in the `onCreate()` with this: `new getPhotosData().execute();` Sorry for making you guess. I will update the post with a few other things as soon as I remember where I have stored the source (it's quite old now :-)) – Siddharth Lele Dec 16 '14 at 13:36
  • Thanks :) I am also trying to get the user's photos with the FB SDK like you are in this code, But I am running into an issue where I get NullPointerException in the adapter class on getCount. I am not sure why I am getting it. For the URL I am using URL = "https://graph.facebook.com/" + "me/albums" + "/photos&access_token=" + s.getAccessToken() + "?limit=10"; – Jack Dec 16 '14 at 13:56
  • So that should give me the users albums, but that is the only reason I can think of as to why I am getting NPE in the adapter on getCount :( – Jack Dec 16 '14 at 13:58
  • I have posted a full question to make it easier for you :) http://stackoverflow.com/questions/27506822/npe-getcount-in-adapter-when-trying-to-get-user-albums-using-fb-sdk – Jack Dec 16 '14 at 14:25
  • 1
    Truly a day-saver..@IceMAN Thanks a ton for this answer!! :) – PunitD Dec 10 '15 at 11:35
  • I am getting more `visibleItemCount` than the number of items actually being displayed. – Iqbal Feb 04 '16 at 10:01
  • @Iqbal: Post it as a different question. This answer does not really deal with it. Comment with the link to your question and I'll see if I can help out. ;-) – Siddharth Lele Feb 04 '16 at 10:12
4

You can, take a look to Using adapter Views and GridView from Android Documentation. The most important thing is that the adapter call the method getView passing only the position of the entries showing on screen, and asking for different positions when user scrolls.

The easy way to do is download the required image on the getView method of your adapter with and AsyncTask.

There is an example

sabadow
  • 5,095
  • 3
  • 34
  • 51
0

Talking from experience, it's tricky to achieve smooth scrolling (and overall responsiveness) while consuming memory reasonably.

It would be a good idea to look for existing solutions first, e.g., start here: Lazy load of images in ListView

We ended up with a custom proprietary solution. It is a background thread that queues download requests and downloads and caches on the external storage only the images that are still visible. When a new image arrives, the view gets notified and decides when to notify the adapter to update.

It also saves the bandwidth, which was important in some cases.

Community
  • 1
  • 1
full.stack.ex
  • 1,747
  • 2
  • 11
  • 13
0

I found IceMAN's answer very useful, but I also recommend avoid using two AsyncTasks and you can make this easily.

You need to create a universal method to fetch needed data, where you can make an if/else condition (as an example):

        movies = fetchMovie.execute(sort).get();
        if (movies == null) {
            movieList = new ArrayList<>();
        } else if (addMovies) {
            movieList.addAll(movies);
        } else {
            movieList = movies;
        }

addMovies is a boolean in your onScroll method. In AsyncTask provide current page in query URL and voila - you made your code smaller :)

Dmytro Karataiev
  • 1,214
  • 1
  • 14
  • 19