4

I'm building a simple IMDB app and I'm almost done save for one tiny detail. The API(http://www.omdbapi.com/) supplies only 10 movies at a time, and the user can specify which "page" do they want. I would like to retrieve all entries. My code looks something like this:

//This populates the list
private void populateList(String title) {
    myAPI.getSearchResults(title, page).enqueue(new Callback<Movies>() {
        @Override
        public void onResponse(Call<Movies> call, Response<Movies> response) {
            movies = response.body().getSearch();
            recyclerView.setAdapter(new ItemAdapter(movies));
            recyclerView.addOnItemTouchListener(
                new ItemClickableListener(getActivity(), new   ItemClickableListener.OnItemClickListener() {
                    @Override
                    public void onItemClick(View view, int position) {
                        String id = movies.get(position).getImdbID();
                        showDetails(id, view);
                    }
                }));
        }

        @Override
        public void onFailure(Call<Movies> call, Throwable t) {
            Log.d(TAG, "Error: " + t);
        }
    });
}

And in my interface:

//For populating the list
@GET("?")
Call<Movies> getSearchResults(@Query("s") String title, @Query("page") int pages);

There is a way to know how many entries there are in total but the query must run at least once to retrieve that info. I tried fixing it with a "do...while" loop and adding each consecutive batch of movies to a list and only then populating the RecyclerView but it just wouldn't work (it would leave the loop without displaying a thing). Maybe I overlooked something and that is the correct answer, but even then - Isn't there a more elegant approach?

Haruspik
  • 412
  • 5
  • 17

2 Answers2

1

I think you need EndlessRecyclerView to retrieve pages ten by ten. with following code:

mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
mAdapter = new MyAdapter(getActivity(), this);
scrollListener = new EndlessRecyclerOnScrollListener((LinearLayoutManager) mRecyclerView.getLayoutManager()) {
    @Override
    public void onLoadMore(int page) {
        callWebservice(page);
    }
};
mRecyclerView.addOnScrollListener(scrollListener);
mRecyclerView.setAdapter(mAdapter);

When callWebservice is done add Items to your list:

        @Override
        public void onResponse(Call<List<ShortVideoModel>> call, Response<List<ShortVideoModel>> response) {
             mAdapter.addItems(response.body());
        }
Amir
  • 16,067
  • 10
  • 80
  • 119
0

I ended up checking out EndlessRecyclerView and it works almost perfectly, but I've run into a few issues so I'm posting the code here. It kept stacking listeners and adapters so I swap them. It also kept scrolling up each time data is inserted so I forced it to stay but it's little jittery.

public class SearchFragment extends Fragment {
final String TAG = "LOG.SearchFragment";
final String baseUrl = "http://www.omdbapi.com/";
Button searchButton;
EditText searchField;
RecyclerView recyclerView;
LinearLayoutManager llm;
String title = "";
int page = 1;
List<Search> movies;
Gson gson;
Retrofit retrofit;
MyAPI myAPI;
ItemClickableListener listener;
EndlessRecyclerOnScrollListener scrollListener;
int firstItem;
float topOffset;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    Log.d(TAG, "Starting SearchFragment...");
    return inflater.inflate(R.layout.search_fragment, container, false);
}

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    //Preparing RecyclerView
    recyclerView = (RecyclerView) getActivity().findViewById(R.id.recycler_view);
    llm = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
    recyclerView.setLayoutManager(llm);
    setOnScrollManager();

    //List for the movies
    movies = new ArrayList<>();

    //UI
    searchField = (EditText) getActivity().findViewById(R.id.search_field);
    searchButton = (Button) getActivity().findViewById(R.id.search_button);
    searchButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if (!searchField.getText().toString().equals("")) {
                gson = new GsonBuilder().create();
                retrofit = new Retrofit.Builder()
                        .baseUrl(baseUrl)
                        .addConverterFactory(GsonConverterFactory.create(gson))
                        .build();
                myAPI = retrofit.create(MyAPI.class);
                title = searchField.getText().toString();
                movies.clear();
                page=1;
                setOnScrollManager();
                fetchMovies(title, page);
            }
        }
    });
}

private void setOnScrollManager() {
    if (scrollListener!=null) recyclerView.removeOnScrollListener(scrollListener);
    scrollListener = new EndlessRecyclerOnScrollListener((LinearLayoutManager) recyclerView.getLayoutManager()) {
        //This happens when user scrolls to bottom
        @Override
        public void onLoadMore(int newPage) {
            Log.d(TAG, "OnLoadMore "+newPage);
            //Preparing the scroll
            firstItem = llm.findFirstVisibleItemPosition();
            View firstItemView = llm.findViewByPosition(firstItem);
            topOffset = firstItemView.getTop();

            //Getting new page
            page=newPage;
            fetchMovies(title, page);
        }
    };
    recyclerView.addOnScrollListener(scrollListener);
}

//This populates the list
private void fetchMovies(String title, int page) {
    Log.d(TAG, "Getting "+title+", page "+page);
    myAPI.getSearchResults(title, page).enqueue(new Callback<Movies>() {
        @Override
        public void onResponse(Call<Movies> call, Response<Movies> response) {
            if (movies.size()==0) Toast.makeText(getActivity(), "No movies found", Toast.LENGTH_SHORT).show();
            movies.addAll(response.body().getSearch());
            //We swap the adatper's content when user scrolls down and loads more data
            recyclerView.setRecycledViewPool(new RecyclerView.RecycledViewPool());
            recyclerView.swapAdapter(new ItemAdapter(movies), true);

            //Scrolling
            Log.d(TAG, "Scrolling to "+firstItem);
            llm.scrollToPositionWithOffset(firstItem, (int) topOffset);

            //We avoid stacking up listeners
            if (listener!=null) recyclerView.removeOnItemTouchListener(listener);
            listener = new ItemClickableListener(getActivity(), new   ItemClickableListener.OnItemClickListener() {
                @Override
                public void onItemClick(View view, int position) {
                    String id = movies.get(position).getImdbID();
                    showDetails(id, view);
                }
            });
            recyclerView.addOnItemTouchListener(listener);
        }

        @Override
        public void onFailure(Call<Movies> call, Throwable t) {
            Log.d(TAG, "Error: " + t);
        }
    });
}

//This gets the movie details
private void showDetails(String id, final View view){
    myAPI.getDetails(id).enqueue(new Callback<MovieDetails>() {
        @Override
        public void onResponse(Call<MovieDetails> call, Response<MovieDetails> response) {
            showPopup(response.body(), view);
        }

        @Override
        public void onFailure(Call<MovieDetails> call, Throwable t) {
            Log.d(TAG, "Error: " + t);
        }
    });
}

//This displays the movie details
private void showPopup(MovieDetails details, View anchorView) {

    View popupView = getActivity().getLayoutInflater().inflate(R.layout.popup_layout, null);

    PopupWindow popupWindow = new PopupWindow(popupView,
            RecyclerView.LayoutParams.WRAP_CONTENT, RecyclerView.LayoutParams.WRAP_CONTENT);

    TextView title = (TextView) popupView.findViewById(R.id.movie_detail_title);
    TextView year = (TextView) popupView.findViewById(R.id.movie_detail_year);
    TextView rating = (TextView) popupView.findViewById(R.id.movie_detail_rating);
    TextView director = (TextView) popupView.findViewById(R.id.movie_detail_director);
    TextView stars = (TextView) popupView.findViewById(R.id.movie_detail_stars);
    TextView desc = (TextView) popupView.findViewById(R.id.movie_detail_desc);

    title.setText(details.getTitle());
    title.setTextColor(Color.parseColor("#ffffff"));
    year.setText(details.getYear());
    year.setTextColor(Color.parseColor("#ffffff"));
    rating.setText(details.getImdbRating()+"/10");
    rating.setTextColor(Color.parseColor("#ffffff"));
    director.setText("Dir: "+details.getDirector());
    director.setTextColor(Color.parseColor("#ffffff"));
    stars.setText("Stars: "+details.getActors());
    stars.setTextColor(Color.parseColor("#ffffff"));
    desc.setText(details.getPlot());
    desc.setTextColor(Color.parseColor("#ffffff"));

    UrlValidator urlValidator = new UrlValidator();
    if (urlValidator.isValid(details.getPoster())) {
        ImageView poster = (ImageView) popupView.findViewById(R.id.movie_detail_poster);
        ImageLoader imageLoader = ImageLoader.getInstance();
        imageLoader.displayImage(details.getPoster(), poster);
    }

    // If the PopupWindow should be focusable
    popupWindow.setFocusable(true);

    // If you need the PopupWindow to dismiss when when touched outside
    popupWindow.setBackgroundDrawable(new ColorDrawable(Color.parseColor("#CC000000")));

    int location[] = new int[2];

    // Get the View's(the one that was clicked in the Fragment) location
    anchorView.getLocationOnScreen(location);

    // Using location, the PopupWindow will be displayed right under anchorView
    popupWindow.showAtLocation(anchorView, Gravity.NO_GRAVITY,
            location[0], location[1] + anchorView.getHeight());

}
}
Haruspik
  • 412
  • 5
  • 17