TheMovieDb API – Part 6 – The Movies Adapter

Sample code for a movie adapter

Hey! welcome back to this series on implementing TheMovieDb API. As always remember all the code for this series will be in this GitHub repo today we are jumping straight to Java once more, last week post was all about the design of the Movie view today we are building the bridge that connects it all, the movies adapter.

Let’s go! First keep in mind this is 2016 and beyond we need to re-use our views and who better than the RecyclerView for that job? We are going to create something called a ‘ViewHolder’ which it’s only job is to hold a reference of the views within our movie_view.xml and give it to the RecyclerView each time a new Movie object is given to him and reuse it. This view holder should also implement an interface to handle clicks on our actions buttons, remember RecyclerView is a decoupled version of ListView meaning all of these does not come out of the box. Tricky right? Let’s see how this looks in action:

Let’s create our interface responsible for the action clicks, I called it MovieItemClickListener:

First the movie viewholder, mine I called it MovieSingleRowViewHolder:

public interface MovieItemClickListener {
    void onMovieActionClick(View v, int position);
}

Simple so far right? The onMovieActionClick will have a View reference so we can know which action button is pressed along with the movie position as well. Awesome!

Now we can prepare our viewholder, I called it MovieSingleRowViewHolder, a bit long but in future posts you’ll see why. It is prepared like follows:

public class MovieSingleRowViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    public ImageView posterImageView;
    public TextView directedByTextView, titleTextView;
    public Button detailsButton, reviewsButton;
    public AppCompatRatingBar popularityRatingBar;
    private MovieItemClickListener itemClickedListener;

    public MovieSingleRowViewHolder(View view, MovieItemClickListener itemClickedListener) {
        super(view);
        posterImageView = (ImageView) view.findViewById(R.id.image);
        titleTextView = (TextView) view.findViewById(R.id.title);
        directedByTextView = (TextView) view.findViewById(R.id.directed_by);
        popularityRatingBar = (AppCompatRatingBar) view.findViewById(R.id.popularity);
        detailsButton = (Button) view.findViewById(R.id.details);
        reviewsButton = (Button) view.findViewById(R.id.reviews);

        //action listeners
        detailsButton.setOnClickListener(this);
        reviewsButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (itemClickedListener == null) {
            throw new IllegalArgumentException("No item listener set yet.");
        } else
            itemClickedListener.onMovieActionClick(v, this.getLayoutPosition());
    }
}

Notice that I’m just doing a typical findViewById() operation over my views. I have also implement our custom interface so we can set it out for our details and reviews action button. WARNING, depending on how you created your movie_view.xml previously you will mapped out what applies in your particularly case, if you have being following me then you should be good.

Alright! Now for the bridge of it all, the adapter, Let’s go part by part before seeing it all together. First create a new class called MoviesAdapter and make it extend your RecyclerView.Adapter<YourViewHolder>:

public class MoviesAdapter extends RecyclerView.Adapter<MovieSingleRowViewHolder> {

    private final static int VIEW_SINGLE_ROW = 1;
    private Context context;
    private Picasso picasso;
    private List<Movie> movies;
    private MovieItemClickListener itemClickedListener;

....

You will also noticed we have some members, context is used to access resources across the device, Picasso a powerful and easy to use image loading library is here too and it needs a Context object to work. We will also have a List<Movie> of movies so we can add/remove from our adapter. Finally we have an itemClickListener which will be passed down to our viewholder when inflation of it occurs. With all these in place we can set out a constructor like:

public MoviesAdapter(Context context, MovieItemClickListener clickListener) {
    this.context = context;
    this.movies = new ArrayList<>();
    this.picasso = Picasso.with(context);
    this.itemClickedListener = clickListener;
}

Next we will prepare onCreateViewHolder, which is a method that got automatically implemented out when we extended from RecclerViewAdapter<T>:

@Override
public MovieSingleRowViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View v;
    switch (viewType) {
        case VIEW_SINGLE_ROW:
            v = LayoutInflater.from(parent.getContext()).inflate(R.layout.movie_view, parent, false);
            return new MovieSingleRowViewHolder(v, itemClickedListener);
        default:
            return null;
    }
}

Nothing too fancy here, just checking if our ViewHolder type is single rowed (meaning when getItemViewtType() is internally called we get 1 as its returned value) if so we inflate our movie_view and passed it out to the viewholder constructor. Now we have a viewholder ready to go.

So now when the viewholder gets binded we can set out values such as the title, popularity and the poster image:

@Override
public void onBindViewHolder(MovieSingleRowViewHolder holder, int position) {
    Movie movie = movies.get(position);
    if (movie != null) {

        //mapping
        holder.titleTextView.setText(movie.title);
        holder.popularityRatingBar.setRating((movie.popularity == null) ? 0 : movie.popularity.floatValue());
        picasso.load(Image.BASE_URL + Image.SIZE_W500 + movie.poster_path).into(holder.posterImageView);
    }
}

*For Image.BASE_URL we and size we used TheMovieDb.org url paths to retrieve the appropiate size, if you cannot find it in the documentation simply copy paste from the repo. Now moving on the rest of the method the following two are self explanatory:

@Override
public int getItemViewType(int position) {
    return 1;
}

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

Now let’s add our clear() and getAll() methods:

/**
 * Clears our @member movies.
 */
public void clear() {
    int size = movies.size();
    movies.clear();
    notifyItemRangeRemoved(0, size);
}

Easy right? notifyItemRangeRemoved(…) will tell recycler view from where to where items were removed so he can update his index.

/**
 * Retrieve a copy of @member movies
 *
 * @return movies List
 */
public List<Movie> getAll() {
    return movies;
}

/**
 * Returns a Movie object for the given position within @member movies.
 *
 * @param position
 * @return
 */
public Movie get(int position) {
    return movies.get(position);
}

Self explanatory right? Finally the addAll() and IsNotEmpty() methods which will make our adapter complete for now in terms of functionality:

/**
 * Adds all Movie objects to the current @member movies.
 * and notifies it to the adapter.
 *
 * @param movies
 */
public void addAll(List<Movie> movies) {
    if (this.movies.size() > 0) {
        int mSize = this.movies.size();
        this.movies.addAll(mSize, movies);
        notifyItemRangeInserted(mSize, movies.size());
    } else {
        this.movies.addAll(movies);
        notifyItemRangeInserted(0, movies.size());
    }
}

/**
 * Checks if our @member movies has items.
 *
 * @return
 */
public boolean IsNotEmpty() {
    return movies.size() > 0;
}

AddAll is a bit tricky because it takes in consideration ordering of items, if the current size == 0 meaning they are no movies in the collection yet then it’s safe to assume we can add them all at the default index (n0, n1, n2…) on the other hand if we have items already then we must push them only to the current size of the collection in order to it behave as expected.

Alright! That’s it you have build out your interface, viewholder and the bridge of information the adapter!

Until next time!

Joel

Please follow and like us:
Facebook
Twitter
Joel Sosa

Author: Joel Sosa

Android nanodegree holder | Graduate Student @GeorgiaTech CS Interactive Intelligence | @gdgpuertorico Organizer