Search code examples
androidrestapiretrofitimdb

Using retrofit with IMDB API


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?


Solution

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