Rotten Tomatoes Networking Tutorial
⚠️ Historical tutorial — the API this guide is built on no longer exists. The public Rotten Tomatoes API v1.0 used throughout this page has been retired: its hosts (
api.rottentomatoes.comanddeveloper.rottentomatoes.com) no longer resolve in DNS, so the API requests, key-registration links, andflixster.composter URLs below do not work today. The Rotten Tomatoes Developer Network had already restricted the API to approved partners by late 2016, stating it “no longer support[s] unauthorized use of our data (e.g. unofficial projects, non-user facing data integrations)” — see the archived developer portal. The page is preserved as a historical reference for the networking, JSON-deserialization, and adapter patterns it demonstrates. To build a working version of this app, use a live movie API with self-service key registration such as The Movie Database (TMDB), and see Sending and Managing Network Requests, Consuming APIs with Retrofit, and Using OkHttp for current networking guidance.
This tutorial covered building a basic app for displaying popular movies currently in theaters using the (now retired) unauthenticated RottenTomatoes API.
Check out this slide deck for a basic walkthrough.
App
Let’s build an Android app step by step. The app will display a list of popular movies currently in theaters which includes the poster, title, and cast of each film.
The app will do the following:
- Fetch the box office movies from the Box Office Movie API in JSON format
- Deserialize the JSON data for each of the movies into
BoxOfficeMovieobjects - Build an array of
BoxOfficeMovieobjects and create anArrayAdapterfor those movies - Define
getViewto define how to inflate a layout for each movie row and display each movie’s data. - Attach the adapter for the movies to a ListView to display the data on screen
To do this, we can organize the different components into the following objects:
RottenTomatoesClient- Responsible for executing the API requests and retrieving the JSONBoxOfficeMovie- Model object responsible for encapsulating the attributes for each individual movieBoxOfficeMoviesAdapter- Responsible for mapping eachBoxOfficeMovieto a particular view layoutBoxOfficeActivity- Responsible for fetching and deserializing the data and configuring the adapter
The full source code for this app can be downloaded here for review as well.
Tutorial (historical reference)
Setup with the API
RottenTomatoes had an extensive API which included a Movie Search, Lists, Box Office Movies and much more. This tutorial used one of these APIs to create a simple demonstration of networking with Android. (These documentation links are on the retired developer.rottentomatoes.com host and are kept only as a record of the original sources.)
At the time, every API call had to be authorized with an API key, which required registering for an account on the developer portal and then setting up a key.
Hint: The sample key 9htuhtcb4ymusd73d4z6jxcj that this demo originally provided no longer works — the API host itself is offline.
Explore the API Endpoints
In particular, this app used the Box Office Movies endpoint to retrieve a list of movies currently playing in theaters. The url to retrieve this data was http://api.rottentomatoes.com/api/public/v1.0/lists/movies/box_office.json?apikey=<api-key-here>. The response was as follows:
{
"movies": [{
"id": "770687943",
"title": "Harry Potter and the Deathly Hallows - Part 2",
"year": 2011,
"mpaa_rating": "PG-13",
"runtime": 130,
"critics_consensus": "Thrilling, powerfully acted, and visually dazzling...",
"release_dates": {"theater": "2011-07-15"},
"ratings": {
"critics_rating": "Certified Fresh",
"critics_score": 97,
"audience_rating": "Upright",
"audience_score": 93
},
"synopsis": "Harry Potter and the Deathly Hallows, is the final adventure...",
"posters": {
"thumbnail": "http://content8.flixster.com/movie/11/15/86/11158674_mob.jpg",
"profile": "http://content8.flixster.com/movie/11/15/86/11158674_pro.jpg",
"detailed": "http://content8.flixster.com/movie/11/15/86/11158674_det.jpg",
"original": "http://content8.flixster.com/movie/11/15/86/11158674_ori.jpg"
},
"abridged_cast": [
{
"name": "Daniel Radcliffe",
"characters": ["Harry Potter"]
},
{
"name": "Rupert Grint",
"characters": [
"Ron Weasley",
"Ron Wesley"
]
}
]
},
{
"id": "770687943",
...
}]
}
As you can see the API returns a dictionary with a single “movies” key which contains an array of json dictionaries which each represent the data for a particular film. This is the response we will parse and use to create our application.
Install Library Dependencies
Before we continue, we need to setup the android-async-http-client for sending asynchronous network requests.
Let’s also install a library for remote image loading called Picasso so we can easily display movie posters.
Edit the app/build.gradle file and add them as dependencies that can be downloaded via a remote repository:
repositories {
jcenter()
}
dependencies {
// ...
implementation 'com.squareup.picasso:picasso:2.5.2'
implementation 'com.loopj.android:android-async-http:1.4.6'
}
Then you need to sync with gradle and make sure there are no errors.
Download Assets
Make sure to download the two placeholder movie posters:
Copy and paste both small_movie_poster.png and large_movie_poster.png into res/drawable for use later. You can drag and drop these files into the appropriate folders assuming you have selected the Project dropdown inside Android Studio.
Setup Internet Permission
Add the proper internet permissions to the AndroidManifest.xml within the manifest node:
<uses-permission android:name="android.permission.INTERNET" />
Once you add this, network requests can be executed. Make sure that the permissions are defined before the <application>…</application> is declared. Otherwise, you may notice that your network requests are quietly being ignored.
Setup Basic Activity Layout
Generate a project with the name “RottenTomatoesDemo”, a minimum API of 10 and a first activity called BoxOfficeActivity. In the res/layout/activity_box_office.xml, let’s drop out a ListView with the id lvMovies:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BoxOfficeActivity" >
<ListView
android:id="@+id/lvMovies"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true" >
</ListView>
</RelativeLayout>

Construct API Client
Let’s generate a Java class that will act as our RottenTomato API client for sending out the network requests to the specific endpoints. Create a Java class called RottenTomatoesClient with the following code:
public class RottenTomatoesClient {
private final String API_KEY = "...getkey...";
private final String API_BASE_URL = "http://api.rottentomatoes.com/api/public/v1.0/";
private AsyncHttpClient client;
public RottenTomatoesClient() {
this.client = new AsyncHttpClient();
}
private String getApiUrl(String relativeUrl) {
return API_BASE_URL + relativeUrl;
}
}
Next, let’s define a method for accessing the Box Office Movies API within our client by executing an asynchronous request to the appropriate endpoint:
public class RottenTomatoesClient {
// ...
// http://api.rottentomatoes.com/api/public/v1.0/lists/movies/box_office.json?apikey=<key>
public void getBoxOfficeMovies(JsonHttpResponseHandler handler) {
String url = getApiUrl("lists/movies/box_office.json");
RequestParams params = new RequestParams("apikey", API_KEY);
client.get(url, params, handler);
}
// ...
}
Looking at the JSON response format for the box office movies API, we can understand what information we will be provided:
{
"movies": [{
"id": "770687943",
"title": "Harry Potter and the Deathly Hallows - Part 2",
"year": 2011,
"mpaa_rating": "PG-13",
"runtime": 130,
"critics_consensus": "Thrilling, powerfully acted, and visually dazzling...",
"release_dates": {"theater": "2011-07-15"},
"ratings": {
"critics_rating": "Certified Fresh",
"critics_score": 97,
"audience_rating": "Upright",
"audience_score": 93
},
"synopsis": "Harry Potter and the Deathly Hallows, is the final adventure...",
"posters": {
"thumbnail": "http://content8.flixster.com/movie/11/15/86/11158674_mob.jpg",
"profile": "http://content8.flixster.com/movie/11/15/86/11158674_pro.jpg",
"detailed": "http://content8.flixster.com/movie/11/15/86/11158674_det.jpg",
"original": "http://content8.flixster.com/movie/11/15/86/11158674_ori.jpg"
},
"abridged_cast": [
{
"name": "Daniel Radcliffe",
"characters": ["Harry Potter"]
},
{
"name": "Rupert Grint",
"characters": [
"Ron Weasley",
"Ron Wesley"
]
}
]
},
{
"id": "770687943",
...
}]
}
Define the Data Model
Next, let’s define a model that represents the relevant data for a single box office movie. This movie needs to contain basic info for each movie such as title, year, synopsis, posters.thumbnail, ratings.critics_score and the abridged_cast. Create a Java class called BoxOfficeMovie with the relevant attributes defined:
public class BoxOfficeMovie {
private String title;
private int year;
private String synopsis;
private String posterUrl;
private int criticsScore;
private ArrayList<String> castList;
public String getTitle() {
return title;
}
public int getYear() {
return year;
}
public String getSynopsis() {
return synopsis;
}
public String getPosterUrl() {
return posterUrl;
}
public int getCriticsScore() {
return criticsScore;
}
public String getCastList() {
return TextUtils.join(", ", castList);
}
}
Next, let’s add a factory method for deserializing the JSON response in order to construct an instance of the BoxOfficeMovie class:
public class BoxOfficeMovie {
// ...
// Returns a BoxOfficeMovie given the expected JSON
// BoxOfficeMovie.fromJson(movieJsonDictionary)
// Stores the `title`, `year`, `synopsis`, `poster` and `criticsScore`
public static BoxOfficeMovie fromJson(JSONObject jsonObject) {
BoxOfficeMovie b = new BoxOfficeMovie();
try {
// Deserialize json into object fields
b.title = jsonObject.getString("title");
b.year = jsonObject.getInt("year");
b.synopsis = jsonObject.getString("synopsis");
b.posterUrl = jsonObject.getJSONObject("posters").getString("thumbnail");
b.criticsScore = jsonObject.getJSONObject("ratings").getInt("critics_score");
// Construct simple array of cast names
b.castList = new ArrayList<String>();
JSONArray abridgedCast = jsonObject.getJSONArray("abridged_cast");
for (int i = 0; i < abridgedCast.length(); i++) {
b.castList.add(abridgedCast.getJSONObject(i).getString("name"));
}
} catch (JSONException e) {
e.printStackTrace();
return null;
}
// Return new object
return b;
}
// ...
}
Let’s also add a convenience static method for parsing an array of JSON movie dictionaries into an ArrayList<BoxOfficeMovie> which we will use to manage the box office movie API endpoint response:
public class BoxOfficeMovie {
// ...
// Decodes array of box office movie json results into movie model objects
// BoxOfficeMovie.fromJson(jsonArrayOfMovies)
public static ArrayList<BoxOfficeMovie> fromJson(JSONArray jsonArray) {
ArrayList<BoxOfficeMovie> movies = new ArrayList<BoxOfficeMovie>(jsonArray.length());
// Process each result in json array, decode and convert to movie
// object
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject moviesJson = null;
try {
moviesJson = jsonArray.getJSONObject(i);
} catch (Exception e) {
e.printStackTrace();
continue;
}
BoxOfficeMovie movie = BoxOfficeMovie.fromJson(moviesJson);
if (movie != null) {
movies.add(movie);
}
}
return movies;
}
// ...
}
Construct an ArrayAdapter
Now, we have the API Client (RottenTomatoesClient) and the data Model object (BoxOfficeModel). Final step is to construct an adapter that allows us to translate a BoxOfficeMovie into a particular view. Let’s generate a Java class called BoxOfficeMoviesAdapter which extends from ArrayAdapter<BoxOfficeMovie>:
public class BoxOfficeMoviesAdapter extends ArrayAdapter<BoxOfficeMovie> {
public BoxOfficeMoviesAdapter(Context context, ArrayList<BoxOfficeMovie> aMovies) {
super(context, 0, aMovies);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO: Complete the definition of the view for each movie
return null;
}
}
Now, we need to define a layout to use for visualizing a particular movie. Let’s create a layout file called item_box_office_movie.xml with a RelativeLayout root layout for defining the movie row layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="5dp" >
<ImageView
android:id="@+id/ivPosterImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:maxHeight="70dp"
android:scaleType="fitXY"
android:adjustViewBounds="true"
android:src="@drawable/small_movie_poster" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_toRightOf="@+id/ivPosterImage"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:layout_marginLeft="10dp"
android:layout_height="match_parent">
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:textStyle="bold"
android:hint="The Dark Knight"
android:textSize="12sp" />
<TextView
android:id="@+id/tvCriticsScore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/tvTitle"
android:layout_below="@+id/tvTitle"
android:hint="91%"
android:textSize="12sp" />
<TextView
android:id="@+id/tvCast"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tvCriticsScore"
android:maxLines="1"
android:ellipsize="end"
android:hint="Christian Bale, Joseph-Gordon Levitt, Heath Ledger, Maggie Gylenhall"
android:textSize="12sp" />
</RelativeLayout>
</RelativeLayout>
and now we can fill in the getView method to finish off the BoxOfficeMoviesAdapter:
public class BoxOfficeMoviesAdapter extends ArrayAdapter<BoxOfficeMovie> {
// ...
// Translates a particular `BoxOfficeMovie` given a position
// into a relevant row within an AdapterView
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// Get the data item for this position
BoxOfficeMovie movie = getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.item_box_office_movie, parent, false);
}
// Lookup views within item layout
TextView tvTitle = (TextView) convertView.findViewById(R.id.tvTitle);
TextView tvCriticsScore = (TextView) convertView.findViewById(R.id.tvCriticsScore);
TextView tvCast = (TextView) convertView.findViewById(R.id.tvCast);
ImageView ivPosterImage = (ImageView) convertView.findViewById(R.id.ivPosterImage);
// Populate the data into the template view using the data object
tvTitle.setText(movie.getTitle());
tvCriticsScore.setText("Score: " + movie.getCriticsScore() + "%");
tvCast.setText(movie.getCastList());
Picasso.with(getContext()).load(movie.getPosterUrl()).into(ivPosterImage);
// Return the completed view to render on screen
return convertView;
}
// ...
}
Bringing It Together
Now that we have defined the adapter, let’s plug all of these components together within the BoxOfficeActivity by using the client to send out an API call, deserialize the response into an array of BoxOfficeMovie object and then render those into a ListView using the BoxOfficeMoviesAdapter. Let’s start by initializing the ArrayList, adapter, and the ListView:
public class BoxOfficeActivity extends Activity {
private ListView lvMovies;
private BoxOfficeMoviesAdapter adapterMovies;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_box_office);
lvMovies = (ListView) findViewById(R.id.lvMovies);
ArrayList<BoxOfficeMovie> aMovies = new ArrayList<BoxOfficeMovie>();
adapterMovies = new BoxOfficeMoviesAdapter(this, aMovies);
lvMovies.setAdapter(adapterMovies);
}
}
Now let’s make the call to the box office API from the activity using our client:
public class BoxOfficeActivity extends Activity {
RottenTomatoesClient client;
protected void onCreate(Bundle savedInstanceState) {
// ...
// Fetch the data remotely
fetchBoxOfficeMovies();
}
// Executes an API call to the box office endpoint, parses the results
// Converts them into an array of movie objects and adds them to the adapter
private void fetchBoxOfficeMovies() {
client = new RottenTomatoesClient();
client.getBoxOfficeMovies(new JsonHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject responseBody) {
JSONArray items = null;
try {
// Get the movies json array
items = responseBody.getJSONArray("movies");
// Parse json array into array of model objects
ArrayList<BoxOfficeMovie> movies = BoxOfficeMovie.fromJson(items);
// Load model objects into the adapter
for (BoxOfficeMovie movie : movies) {
adapterMovies.add(movie); // add movie through the adapter
}
adapterMovies.notifyDataSetChanged();
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
}
After running the app, we should see the populated box office movie data:

The full source code for this app can be downloaded here for review as well.
Adding a Detail View
Suppose we wanted to add a detail view which would display more details about a movie when the movie was selected within the list. In order to achieve that we would need to:
- Generate an activity for the details view
- Define the detail Layout XML with views
- Setup an item click listener for the list of movies
- Fire an intent when a user selects a movie from the list
- Pass the movie through the intent and populate the detail view
First, let’s generate the activity that will display all the details by selecting File => New => Other => Android Activity and naming our activity “BoxOfficeDetailActivity”. This activity should simply display additional information about the film for people to view, let’s edit the layout res/layout/activity_box_office_detail.xml to include all the movie details and a larger poster:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="5dp"
tools:context=".BoxOfficeDetailActivity" >
<!-- @drawable/large_movie_poster sourced from
http://content8.flixster.com/movie/11/15/86/11158674_pro.jpg -->
<ImageView
android:id="@+id/ivPosterImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:maxHeight="160dp"
android:scaleType="fitXY"
android:adjustViewBounds="true"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/ivPosterImage"
android:textStyle="bold"
android:layout_marginLeft="8dp"
android:layout_toRightOf="@+id/ivPosterImage"
android:text="The Dark Knight"
android:textSize="18sp" />
<TextView
android:id="@+id/tvCriticsScore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/tvTitle"
android:layout_below="@+id/tvTitle"
android:layout_marginTop="5dp"
android:text="Critics Score: 93%"
android:textSize="14sp" />
<TextView
android:id="@+id/tvAudienceScore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/tvCriticsScore"
android:layout_below="@+id/tvCriticsScore"
android:layout_marginTop="5dp"
android:text="Audience Score: 93%"
android:textSize="14sp" />
<TextView
android:id="@+id/tvCast"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/tvAudienceScore"
android:layout_below="@+id/tvAudienceScore"
android:layout_marginTop="5dp"
android:text="Christian Bale, Joseph-Gordon Levitt"
android:textSize="12sp" />
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/ivPosterImage"
android:layout_margin="10dp" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/tvCriticsConsensus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is an excellent movie that has action and romance" />
<TextView
android:id="@+id/tvSynopsis"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="This is a story about a protagonist defeating an antagonist" />
</LinearLayout>
</ScrollView>
</RelativeLayout>
Next, we want to be able to pass the movie object from the list to this detail view, so let’s make our BoxOfficeMovie serializable:
public class BoxOfficeMovie implements Serializable {
private static final long serialVersionUID = -8959832007991513854L;
// ...
}
We also want to be able to access a few key attributes of the API data for the detail page such as a larger picture and critics consensus:
public class BoxOfficeMovie implements Serializable {
// ...
private String largePosterUrl;
private String criticsConsensus;
private int audienceScore;
public static BoxOfficeMovie fromJson(JSONObject jsonObject) {
// ...
b.largePosterUrl = jsonObject.getJSONObject("posters").getString("detailed");
b.criticsConsensus = jsonObject.getString("critics_consensus");
b.audienceScore = jsonObject.getJSONObject("ratings").getInt("audience_score");
// ...
}
// ...
public String getLargePosterUrl() {
return largePosterUrl;
}
public String getCriticsConsensus() {
return criticsConsensus;
}
public int getAudienceScore() {
return audienceScore;
}
}
Let’s now add an item click handler to our BoxOfficeActivity which will launch the detail view with an intent:
public class BoxOfficeActivity extends Activity {
// ...
public static final String MOVIE_DETAIL_KEY = "movie";
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
setupMovieSelectedListener();
}
// ...
public void setupMovieSelectedListener() {
lvMovies.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View item, int position, long rowId) {
// Launch the detail view passing movie as an extra
Intent i = new Intent(BoxOfficeActivity.this, BoxOfficeDetailActivity.class);
i.putExtra(MOVIE_DETAIL_KEY, adapterMovies.getItem(position));
startActivity(i);
}
});
}
}
Now, let’s populate the data from the movie into the details view with BoxOfficeDetailActivity:
public class BoxOfficeDetailActivity extends Activity {
private ImageView ivPosterImage;
private TextView tvTitle;
private TextView tvSynopsis;
private TextView tvCast;
private TextView tvAudienceScore;
private TextView tvCriticsScore;
private TextView tvCriticsConsensus;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_box_office_detail);
// Fetch views
ivPosterImage = (ImageView) findViewById(R.id.ivPosterImage);
tvTitle = (TextView) findViewById(R.id.tvTitle);
tvSynopsis = (TextView) findViewById(R.id.tvSynopsis);
tvCast = (TextView) findViewById(R.id.tvCast);
tvCriticsConsensus = (TextView) findViewById(R.id.tvCriticsConsensus);
tvAudienceScore = (TextView) findViewById(R.id.tvAudienceScore);
tvCriticsScore = (TextView) findViewById(R.id.tvCriticsScore);
// Use the movie to populate the data into our views
BoxOfficeMovie movie = (BoxOfficeMovie)
getIntent().getSerializableExtra(BoxOfficeActivity.MOVIE_DETAIL_KEY);
loadMovie(movie);
}
// Populate the data for the movie
public void loadMovie(BoxOfficeMovie movie) {
// Populate data
tvTitle.setText(movie.getTitle());
tvCriticsScore.setText(Html.fromHtml("<b>Critics Score:</b> " + movie.getCriticsScore() + "%"));
tvAudienceScore.setText(Html.fromHtml("<b>Audience Score:</b> " + movie.getAudienceScore() + "%"));
tvCast.setText(movie.getCastList());
tvSynopsis.setText(Html.fromHtml("<b>Synopsis:</b> " + movie.getSynopsis()));
tvCriticsConsensus.setText(Html.fromHtml("<b>Consensus:</b> " + movie.getCriticsConsensus()));
// R.drawable.large_movie_poster from
// http://content8.flixster.com/movie/11/15/86/11158674_pro.jpg -->
Picasso.with(this).load(movie.getLargePosterUrl()).
placeholder(R.drawable.large_movie_poster).
into(ivPosterImage);
}
}
With this, we should now be able to view the movie detail view:
The full source code for this app can be downloaded here for review as well.

