Solution to Challenge: Paging


#1

Here are My version of solution to “Challenge: Paging”.It is based on Challenge 1 due to the requirement to access page information.
Major changes that I made:

  1. Two Class “GalleryPage”, “PhotoPages” created and made static singleton so that the paging information can be access throughout the project.
  2. onScrollListener added for recyclerView
  3. page display text added to the bottom of the fragment.
  4. setupAdapter() is only called once when mPhotoRecyclerView.getAdapter()==null
  5. onPostExecute() is delegated to process extra UI updates
  6. FlickrFetchr.parseItems() no longer needed

GalleryPage.java

import com.google.gson.annotations.SerializedName;
import java.util.List;

public class GalleryPage {

    PhotoPages photos;
    String stat;

    public static GalleryPage sGalleryPage;

    public static GalleryPage getGalleryPage() {
        if (sGalleryPage == null) {
            sGalleryPage = new GalleryPage();
        }
        return sGalleryPage;
    }

    private GalleryPage() {  }

    public List<GalleryItem> getGalleryItemList() {
        return photos.getPhotoList();
    }

    public int getTotalPages() {
        return photos.pages;
    }
    public int getItemPerPage() {
        return photos.perpage;
    }
}

 class PhotoPages {
     int page;
     int pages;
     int perpage;
     int total;
    @SerializedName("photo")
    List<GalleryItem> photoList;

    List<GalleryItem> getPhotoList() {
        return photoList;
    }
 }

PhotoFragmentGallery.java

import android.os.AsyncTask;
import android.os.Bundle;

import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.TextView;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class PhotoGalleryFragment extends Fragment {

    private static String TAG = "PhotoGallery";
    private RecyclerView      mPhotoRecyclerView;
    private TextView          mCurrentPageText;
    private GridLayoutManager mGridManager;
    private List<GalleryItem> mItems = new ArrayList<>();
    private int pageFetched = 0;
    boolean asyncFetching   = false;
    int     mCurrentPage    = 1;
    int     mMaxPage        = 1;
    int     mItemsPerPage   = 1;

    public static PhotoGalleryFragment newInstance(){
        return new PhotoGalleryFragment();
    }

    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
        new  FetchItemTask().execute();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState){
        View v =inflater.inflate(R.layout.fragment_photo_gallery,container,false);
        mCurrentPageText = (TextView) v.findViewById(R.id.currentPageText);
        mPhotoRecyclerView = (RecyclerView) v.findViewById(R.id.photo_recycler_view);
        mPhotoRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {                    
                mGridManager= new GridLayoutManager(getActivity(),3);
                mPhotoRecyclerView.setLayoutManager(mGridManager);
                mPhotoRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });

        mPhotoRecyclerView.addOnScrollListener( new RecyclerView.OnScrollListener() {

           @Override
           public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
               int lastVisibleItem = mGridManager.findLastVisibleItemPosition();
               int firstVisibleItem = mGridManager.findFirstVisibleItemPosition();
               Log.d(TAG, "Scrolling, First Item : "+ firstVisibleItem +" Last Item : "+lastVisibleItem);
               if (!(asyncFetching) && (dy > 0) && (mCurrentPage < mMaxPage) && (lastVisibleItem >= (mItems.size() - 1))) {
                   Log.d(TAG, "Fetching more items");
                   new FetchItemTask().execute();
               } else updatePageText(firstVisibleItem);
           }
       });

        if(mPhotoRecyclerView.getAdapter()==null)setupAdapter();

        return v;
    }
    private  void updatePageText(int pos){
        mCurrentPage = pos / mItemsPerPage+1;
        mCurrentPageText.setText("Page " + mCurrentPage + " of " + mMaxPage);
    }

    private void setupAdapter(){
        if(isAdded()){
            Log.d(TAG,"Setup Adapter");
            mPhotoRecyclerView.setAdapter(new PhotoAdapter(mItems));
        }
    }

    private class PhotoHolder extends RecyclerView.ViewHolder{
        private TextView mTitleTextView;

        public PhotoHolder(View itemView){
            super(itemView);
            mTitleTextView = (TextView) itemView;
        }

        public void bindGalleryItem(GalleryItem item){
            mTitleTextView.setText(item.getTitle());
        }
    }

    private class PhotoAdapter extends RecyclerView.Adapter<PhotoHolder>{
        private List<GalleryItem> mGalleryItems;

        public PhotoAdapter(List<GalleryItem> galleryItems){
            mGalleryItems = galleryItems;
        }

        @NonNull
        @Override
        public PhotoHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            TextView textView = new TextView(getActivity());
            return new PhotoHolder(textView);
        }

        @Override
        public void onBindViewHolder(@NonNull PhotoHolder photoHolder, int position) {
            GalleryItem galleryItem = mGalleryItems.get(position);
            photoHolder.bindGalleryItem(galleryItem);
        }

        @Override
        public int getItemCount() {
            return mGalleryItems.size();
        }
    }

    private class FetchItemTask extends AsyncTask<Void,Void,List<GalleryItem>>{
        @Override
        protected  List<GalleryItem> doInBackground(Void... params){

            asyncFetching =true;
            return new FlickrFetchr().fetchItems(pageFetched+1);
        }
        @Override
        protected void onPostExecute(List<GalleryItem> items){
            pageFetched++;
            asyncFetching =false;
            mItems.addAll(items);
            GalleryPage pge=GalleryPage.getGalleryPage();
            mMaxPage     = pge.getTotalPages();
            mItemsPerPage= pge.getItemPerPage();
            if(mPhotoRecyclerView.getAdapter()==null)setupAdapter();
            mPhotoRecyclerView.getAdapter().notifyDataSetChanged();
            updatePageText(mGridManager.findFirstVisibleItemPosition());
        }
    }
}

fragment_photo_gallery.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/photo_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toTopOf="@+id/currentPageText"
        app:layout_constraintTop_toTopOf="parent"
        tools:context=".PhotoGalleryActivity">

    </android.support.v7.widget.RecyclerView>

    <TextView
        android:id="@+id/currentPageText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:gravity="center_horizontal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/photo_recycler_view" />

</android.support.constraint.ConstraintLayout>

FlickrFetchr.java

import android.net.Uri;
import android.util.Log;

import com.google.gson.Gson;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class FlickrFetchr {

    private static final String TAG = "PhotoGallery";
    private static final String API_KEY = "YOU_ACTUALL_KEY_HERE";
    public byte[] getUrlBytes(String urlSpec) throws IOException {
        URL url = new URL(urlSpec);
        HttpURLConnection connection = (HttpURLConnection)url.openConnection();
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            InputStream in = connection.getInputStream();
            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                throw new IOException(connection.getResponseMessage() +
                        ": with " +
                        urlSpec);
            }
            int bytesRead = 0;
            byte[] buffer = new byte[1024];
            while ((bytesRead = in.read(buffer)) > 0) {
                out.write(buffer, 0, bytesRead);
            }
            out.close();
            return out.toByteArray();
        } finally {
            connection.disconnect();
        }
    }
    public String getUrlString(String urlSpec) throws IOException {
        return new String(getUrlBytes(urlSpec));
    }

    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);
            Log.i(TAG, "Received JSON: " + jsonString);
            Gson gson       = new Gson();
            GalleryPage.getGalleryPage();
            GalleryPage.sGalleryPage  = gson.fromJson(jsonString,GalleryPage.class);
            items           = GalleryPage.sGalleryPage.getGalleryItemList();
        } catch (IOException ioe) {
            Log.e(TAG, "Failed to fetch items", ioe);
        }
        return items;
    }        
}

Anybody have any suggestions?


Solution to Challenge: Preloading and Caching