Chapter 25 Challenges

Challenge 1 - Gson
I added the dependency to my gradle app file:

compile 'com.google.code.gson:gson:2.8.0'

I created two new classes to match the layout of the JSON data (Plus the existing GalleryItem class):

public class PhotoRequestResult {
    PhotoResults photos;
    String stat;
    List<GalleryItem> getResults() {
        return photos.getPhotolist();
    }
    int getPageCount() {
        return photos.getMaxPages();
    }
    int getItemCount() {
        return photos.getTotal();
    }
    int getItemsPerPage() {
        return photos.getItemsPerPage();
    }
}

public class PhotoResults {
    int page;
    int pages;
    int perpage;
    int total;
    @SerializedName("photo")
    List<GalleryItem> photolist;

    List<GalleryItem> getPhotolist() {
        return photolist;
    }
    int getItemsPerPage() {
        return perpage;
    }
    int getMaxPages() {
        return pages;
    }
    int getTotal() {
        return total;
    }

}

Then I used Gson to parse it:

public List<GalleryItem> fetchItems(int page) {

    List<GalleryItem> items = new ArrayList<>();

    try {
        String url = Uri.parse("https://api.flickr.com/services/rest/")
                .buildUpon()
                .appendQueryParameter("method", "flickr.photos.getRecent")
                .appendQueryParameter("api_key", API_KEY)
                .appendQueryParameter("format", "json")
                .appendQueryParameter("nojsoncallback", "1")
                .appendQueryParameter("page", Integer.toString(page))
                .appendQueryParameter("extras", "url_s")
                .build().toString();
        String jsonString = getUrlString(url);
        Gson gson = new GsonBuilder().create();
        PhotoRequestResult result = gson.fromJson(jsonString, PhotoRequestResult.class);
        maxPages = result.getPageCount();
        itemsPerPage = result.getItemsPerPage();
        totalItems = result.getItemCount();
        items = result.getResults();
    } catch (IOException ioe) {
        Log.e(TAG, "Failed to fetch items", ioe);
    } catch (Exception e) {
        Log.e(TAG, "General error parsing JSON: " + e.getMessage(), e);
    }

    return items;
}

Challenge 2 - Paging
I added a TextView to the layout to report ā€œPage x of yā€ (plus to show some other stuff I was interested in seeing just as a learning exercise). I added variables ā€˜CurrentPageā€™, ā€˜maxPageā€™, and a variety of instance variables to hold data needed for my new TextView.

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.fragment_photo_gallery, container, false);
    mCurrentPageView = (TextView) v.findViewById(R.id.currentPageView);
    mPhotoRecyclerView = (RecyclerView) v.findViewById(R.id.photo_recycler_view);

Then I added a onScrollListener to the recyclerview:

    mPhotoRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            if ( dy > 0 || dy < 0) {
                // Scrolling up or down
                if ( !(amLoading) &&             // Not already processing a page fetch
                    (dy > 0 ) &&                 // Scrolling down
                    (currentPage < maxPage) &&   // Haven't hit the bottom yet
                    mGridLayoutManager.findLastVisibleItemPosition() >= (mItems.size()-1) ) {
                    // We scrolled to the last row of the previously fetched set
                    //
                    // Go fetch more.
                    Log.d(TAG, "Fetching more items");
                    amLoading = true;
                    currentPage++;
                    new FetchItemsTask().execute(); // Also updates current page view
                } else {
                    // Make sure our page value is correct
                    int firstVisibleItem = mGridLayoutManager.findFirstVisibleItemPosition();
                    int calcPage = 0;
                    if ( firstVisibleItem < mItemsPerPage) {
                        calcPage = 1;
                    } else {
                        calcPage = (firstVisibleItem / mItemsPerPage) +
                                (firstVisibleItem % mItemsPerPage == 0 ? 0 : 1);
                    }
                    if ( calcPage != currentPage ) {
                        currentPage = calcPage;
                    }
                    setCurrentPageView(firstVisibleItem);
                }
            }
        }
    });

and

private void setCurrentPageView() {
    setCurrentPageView(-1);
}
private void setCurrentPageView(int firstVisibleItem) {
    if ( firstVisibleItem == -1 ) {
        firstVisibleItem = mGridLayoutManager.findFirstVisibleItemPosition();
    }
    mCurrentPageView.setText("Current Fetched Page: " + currentPage +
                             " of " + ((maxPage==0) ? "<unknown>": maxPage) +
                             ", " + ((itemsPerPage==0)?"<unknown>":itemsPerPage) + " items per page" +
                             ", " + ((maxItems==0)?"<unknown>":maxItems) + " total items" +
                             ", you've scrolled past: " + (firstVisibleItem <= 0 ? 0: firstVisibleItem) +
                             " items.");
}

Then in the FetchItemsTask class, I modified the onPostExecute to update the items appropriately, slightly different behavior on the first time through vs subsequent. You will notice I also updated the FlickrFetchr class to store some of the data I need, which I use the first time through to initialize some instance variables used above in the TextView:

private class FetchItemsTask extends AsyncTask<Void,Void,List<GalleryItem>> {
    @Override
    protected List<GalleryItem> doInBackground(Void...params) {
        return mFlickrFetchr.fetchItems(currentPage);
    }

    @Override
    protected void onPostExecute(List<GalleryItem> items) {
        if ( mItems.size() == 0) {
            // First time in
            maxPage = mFlickrFetchr.getPageCount();
            mItemsPerPage = mFlickrFetchr.getItemsPerPage();
            maxItems = mFlickrFetchr.getTotalItems();
            mItems.addAll(items);
            setupAdapter();
            setCurrentPageView();
        } else {
            final int oldSize = mItems.size();
            mItems.addAll(items);
            mPhotoRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                mPhotoRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                // Scroll to first row of newly added set
                mPhotoRecyclerView.smoothScrollToPosition(oldSize);
                setCurrentPageView();
                amLoading = false;
                }
            });
            mPhotoRecyclerView.getAdapter().notifyDataSetChanged();
        }
    }
}

Challenge 3 - Dynamic Columns
For this one I used a constant column width COLUMNS_SIZE which I set to 200 in the class declarations, then added a ViewTreeObserver, whichis also removed once the layout is complete:

    mPhotoRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            mPhotoRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            // Adjust the columns to fit based on width of RecyclerView
            int width = mPhotoRecyclerView.getWidth();
            mGridColumns = width / COLUMN_SIZE;
            mGridLayoutManager = new GridLayoutManager(getActivity(),mGridColumns);
            mPhotoRecyclerView.setLayoutManager(mGridLayoutManager);
            setupAdapter();
            setCurrentPageView();
        }
    });

thanks for your solutions.
I think this solution for Challenge 3 would be better, because we point column width in dp (140dp) and then use Math.round().

mPhotoRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        float columnWidthInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 140, getActivity().getResources().getDisplayMetrics());
        int width = mPhotoRecyclerView.getWidth();
        int columnNumber = Math.round(width / columnWidthInPixels);
        mPhotoRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), columnNumber));
        mPhotoRecyclerView.scrollToPosition(mFirstVisibleItemPosition);
        mPhotoRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }
});
3 Likes