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