Rotten Tomatoes Walkthrough

Goals for Today

  • Creating a new project in Android Studio
  • Basics of building views and layouts
  • RelativeLayout vs. LinearLayout
  • Setup click events on views
  • Displaying items in a list
  • Handling clicks on items within a list
  • Navigating between screen

Scoping the App

  • Display stream/detail view of movies.
  • Fetch from the network a list of movies
  • Clicking on any item in the list will show the more detailed info.

Create the Layout

  • UI vs XML Editor
  • RelativeLayout vs. LinearLayout

 Creating List

  1. Create a BoxOfficeMovie class (title, poster URL, critics score)
  2. Create an ArrayList of Movies
  3. Get a handle to a ListView
  4. Attach adapter to ListView

Create a BoxOfficeMovie Class

public class BoxOfficeMovie {

    public String title;
    public String posterUrl;
    public float criticsScore;

    public String getScore() {
       return String.valueOf(criticsScore) + "%";
    }
}
   

Build a fake list of movies

public static ArrayList<BoxOfficeMovie> getFakeMovies() {
   ArrayList<BoxOfficeMovie> movies = new ArrayList<>();
   movies.add(new BoxOfficeMovie("Mission: Impossible", "http://bit.ly/1eWVWvz", 93.0f));
   movies.add(new BoxOfficeMovie("Ant-Man", "http://bit.ly/1NdWEjS", 80.0f));
   movies.add(new BoxOfficeMovie("Minions", "http://bit.ly/1J0HaSF", 54.0f));
}

(We'll deal with networking later!)

Create Adapter

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);
     
        // Return the completed view to render on screen
        return convertView;
    }

    // ...
}

Connect everything up

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

}

Add Networking

<uses-permission android:name="android.permission.INTERNET" /> 
repositories {
    jcenter()
}
dependencies {
    // ...
    compile 'com.squareup.picasso:picasso:2.5.2'
    compile 'com.loopj.android:android-async-http:1.4.6'
}

Load remote image

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) {
  
.
.
.
        ImageView ivPosterImage = (ImageView) convertView.findViewById(R.id.ivPosterImage);
    
        Picasso.with(getContext()).load(movie.getPosterUrl()).into(ivPosterImage);
        // Return the completed view to render on screen
        return convertView;
    }

    // ...
}

Create networking client

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;
    }
}

http://api.rottentomatoes.com/api/public/v1.0/lists/movies/box_office.json?apikey=[API key]

 

i.e. API key = 9htuhtcb4ymusd73d4z6jxcj

Add API call

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

http://api.rottentomatoes.com/api/public/v1.0/lists/movies/box_office.json?apikey=[API key]

 

i.e. API key = 9htuhtcb4ymusd73d4z6jxcj

Process movie data

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.setTitle(jsonObject.getString("title"));
              b.setPictureUrl(jsonObject.getJSONObject("posters").getString("thumbnail"));
              b.setCriticsScore(jsonObject.getJSONObject("ratings").getInt("critics_score"));
        } catch (JSONException e) {
            e.printStackTrace();
            return null;
        }
        // Return new object
        return b;
    }

    // ...
}

Make API call

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
                     for (int i = 0; i < items.length(); i++) {
                        BoxOfficeMovie movie = BoxOfficeMovie.fromJson(items.getJSONObject(i));
                        mBoxOfficeMovies.add(movie);
                    }
                    // 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();
                }
            }
        });
    }
}

Navigation Flows

Serializable

public class BoxOfficeMovie implements Serializable {
    // ...
 
}

Passing data

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

Detailed View Layout

Styling/Theming

 <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">#673AB7</item>
        <item name="colorPrimaryDark">#512DA8</item>
        <item name="colorAccent">#FF4081</item>

    </style>

What's left


  • RecyclerView
  • Fragments
  • Drawables
  • Animations/Gestures
  • Services
  • Third-Party Libraries


Help us keep http://guides.codepath.com/android updated!

Rotten Tomatoes Walkthrough

By Roger Hu

Rotten Tomatoes Walkthrough

  • 5,210