Hướng dẫn làm việc với Rotten Tomatoes Api

Bài hướng dẫn này mục đích tạo ra 1 ứng dụng hiển thị các phim phổ biến bằng cách sử dụng Api của Totten Tomatoes

Tổng quan về ứng dụng##

Chúng ta sẽ từng bước tạo ra 1 ứng dụng cho phép hiển thị danh sách các phim nổi bật hiện tại và mỗi 1 phim sẽ bao gồm các thông tin như tên phim, ảnh, và vài thông tin sơ qua về phim. Giao diện của ứng dụng về cơ bản sẽ như sau: app.png Trình tự của lập trình của ứng dụng này sẽ theo các bước sau:

  • Lấy dự liệu phim từ Totten Tomatoes về dưới dạn Json.
  • Xử lý dữ liệu đưa vào đối tượng BoxOfficeMovie
  • Tạo các mảng đối tượng BoxOfficeMovie và nạp vào mảng ArrayAdapter những phim này.
  • Tạo ra phần View cho đối tượng BoxOfficeMovie.
  • Truyền dữ liệu từ đối tượng BoxOfficeMovie vào lớp View vừa tạo ở trên.

Để thực hiện được ứng dụng này, ta cần phải có các phần tách biệt sau:

  1. RottenTomatoesClient: Để gửi và lấy dữ liệu tử Toten Tomatoes.
  2. BoxOfficeMovie: Lớp Model cho từng phim.
  3. BoxOfficeMoviesAdapter: Adapter cho đối tượng 2
  4. BoxOfficeActivity: là phần xử lý dữ liệu của phần 1 và đưa dữ liệu vào Adapter của 3

Thực hiện##

Thiết lập API###

Rotten Tomatoes cung cấp sẵn API cho việc tìm kiếm phim, danh sách hay phân loại các phim, hay còn nhiều hơn nữa. Trong bài này chúng ta sẽ sử dụng một phương thức nhỏ để dùng trong ứng dụng của Android. Để gửi và lấy được dữ liệu từ Rotten Tomatoes thì ta cần phải có Api Key để có thể xác thực quyền được truy cập đến API. Chúng ta không thể tự tạo được Api Key này mà cần phải đăng ký hoặc nếu có rồi thì đăng nhâp để tạo ra Api Key cho mình.

Tìm hiểu về dữ liệu API trả về###

Trong bài này, chúng ta sẽ sử dụng API của phần Box Office Movie để lấy những thông tin phim chiếu trên rạp. Link site để lấy được thông tin phim đang chiếu ở rạp như sau: http://api.rottentomatoes.com/api/public/v1.0/lists/movies/box_office.json?apikey=<api-key-here>. Và dữ liệu trả về có dạng

"movies": [
    {
      "id": "771321699",
      "title": "Star Wars: Episode VII - The Force Awakens",
      "year": 2015,
      "mpaa_rating": "PG-13",
      "runtime": 136,
      "critics_consensus": "",
      "release_dates": {
        "theater": "2015-12-18"
      },
      "ratings": {
        "critics_rating": "Certified Fresh",
        "critics_score": 94,
        "audience_rating": "Upright",
        "audience_score": 91
      },
      "synopsis": "The Star Wars saga continues with this seventh entry -- the first under the Walt Disney Co. umbrella. The film will act as the start of a new trilogy set after the events of Return of the Jedi. J.J. Abrams directs from a script by Michael Arndt. ~ Jeremy Wheeler, Rovi",
      "posters": {
        "thumbnail": "http://resizing.flixster.com/6xF9PyshnlqT6g4OU9xVQFLnr9g=/54x80/dkpu1ddg7pbsk.cloudfront.net/movie/11/20/33/11203306_ori.jpg",
        "profile": "http://resizing.flixster.com/6xF9PyshnlqT6g4OU9xVQFLnr9g=/54x80/dkpu1ddg7pbsk.cloudfront.net/movie/11/20/33/11203306_ori.jpg",
        "detailed": "http://resizing.flixster.com/6xF9PyshnlqT6g4OU9xVQFLnr9g=/54x80/dkpu1ddg7pbsk.cloudfront.net/movie/11/20/33/11203306_ori.jpg",
        "original": "http://resizing.flixster.com/6xF9PyshnlqT6g4OU9xVQFLnr9g=/54x80/dkpu1ddg7pbsk.cloudfront.net/movie/11/20/33/11203306_ori.jpg"
      },
      "abridged_cast": [
        {
          "name": "Harrison Ford",
          "id": "162661579",
          "characters": [
            "Han Solo"
          ]
        },
        {
          "name": "Domhnall Gleeson",
          "id": "770702167",
          "characters": [
            "Gen. Hux"
          ]
        },
        {
          "name": "Daisy Ridley",
          "id": "771488369",
          "characters": [
            "Rey"
          ]
        },
        {
          "name": "John Boyega",
          "id": "771099376",
          "characters": [
            "Finn"
          ]
        },
        {
          "name": "Carrie Fisher",
          "id": "162663355",
          "characters": [
            "Leia"
          ]
        }
      ],
      "alternate_ids": {
        "imdb": "2488496"
      },
      "links": {
        "self": "http://api.rottentomatoes.com/api/public/v1.0/movies/771321699.json",
        "alternate": "http://www.rottentomatoes.com/m/star_wars_episode_vii_the_force_awakens/",
        "cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/771321699/cast.json",
        "reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/771321699/reviews.json",
        "similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/771321699/similar.json"
      }
    },
    {
      "id": "771367711",
      "title": "Alvin and the Chipmunks: The Road Chip",
      "year": 2015,
      "mpaa_rating": "G",
      "runtime": 86,
      "critics_consensus": "",
      "release_dates": {
        "theater": "2015-12-18"
      },
      "ratings": {
        "critics_rating": "Rotten",
        "critics_score": 16,
        "audience_rating": "Spilled",
        "audience_score": 59
      },
      "synopsis": "Through a series of misunderstandings, Alvin, Simon and Theodore come to believe that Dave is going to propose to his new girlfriend in Miami...and dump them. They have three days to get to him and stop the proposal, saving themselves not only from losing Dave but possibly from gaining a terrible stepbrother. (C) Fox",
      "posters": {
        "thumbnail": "http://resizing.flixster.com/HtMKMXfXTtInmIMmTCo-0q6N59s=/54x80/dkpu1ddg7pbsk.cloudfront.net/movie/11/20/27/11202773_ori.jpg",
        "profile": "http://resizing.flixster.com/HtMKMXfXTtInmIMmTCo-0q6N59s=/54x80/dkpu1ddg7pbsk.cloudfront.net/movie/11/20/27/11202773_ori.jpg",
        "detailed": "http://resizing.flixster.com/HtMKMXfXTtInmIMmTCo-0q6N59s=/54x80/dkpu1ddg7pbsk.cloudfront.net/movie/11/20/27/11202773_ori.jpg",
        "original": "http://resizing.flixster.com/HtMKMXfXTtInmIMmTCo-0q6N59s=/54x80/dkpu1ddg7pbsk.cloudfront.net/movie/11/20/27/11202773_ori.jpg"
      },
      "abridged_cast": [
        {
          "name": "Justin Long",
          "id": "162652679",
          "characters": [
            "Alvin"
          ]
        },
        {
          "name": "Matthew Gray Gubler",
          "id": "770692119",
          "characters": [
            "Simon"
          ]
        },
        {
          "name": "Jesse McCartney",
          "id": "519390858",
          "characters": [
            "Theodore"
          ]
        },
        {
          "name": "Kaley Cuoco-Sweeting",
          "id": "771514334",
          "characters": [
            "Eleanor"
          ]
        },
        {
          "name": "Christina Applegate",
          "id": "162663389",
          "characters": [
            "Brittany"
          ]
        }
      ],
      "alternate_ids": {
        "imdb": "2974918"
      },
      "links": {
        "self": "http://api.rottentomatoes.com/api/public/v1.0/movies/771367711.json",
        "alternate": "http://www.rottentomatoes.com/m/alvin_and_the_chipmunks_the_road_chip/",
        "cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/771367711/cast.json",
        "reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/771367711/reviews.json",
        "similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/771367711/similar.json"
      }
    },

Dữ liệu API trả về được lưu trong mảng movies và có nhiều các thông tin liên quan đến phim như tiêu đề, năm, và các hình ảnh,.... Ở ví dụ trên tôi đưa ra có 2 file là Star Wars: Episode VII - The Force AwakensAlvin and the Chipmunks: The Road Chip được đánh giá khác nhau và các thông tin cũng khác nhau

Thiết lập thông số cho Android Studio###

Ở đây chúng ta sẽ sử dụng Android Studio để lập ra ứng dụng này, và đầu tiên ta cần thêm các thư viện bằng cách thêm vào file app/build.gradle

repositories {
    jcenter()
}
dependencies {
    // ...
    compile 'com.squareup.picasso:picasso:2.5.2'
    compile 'com.loopj.android:android-async-http:1.4.6'
}

Tiếp đến ta cần xin quyền truy cập Internet để có thể lấy dữ liệu bằng cách sửa file AndroidManifest.xml ở phần manifest

<uses-permission android:name="android.permission.INTERNET" />

Sau đó tạo nên 1 App cơ bản ban đầu với giao diện là các list item.

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

Và sau khi build file sẽ ra giao diện đơn giản của ứng dụng như dưới list.png

Gửi và lấy dữ liệu từ Rotten Tomatoes###

Như đã nói ở trên thì để lấy dữ liệu các phim trên rạp ta cần lấy từ link Api với Api Key đã tạo ra. Và để gọi đến Api này ta tạo ra 1 lớp RottenTomatoesClient để thực hiện điều này. Và nội dung file đó như sau:

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

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

Tạo đối tượng Box Office Video

Như ta đã biết dữ liệu từ API trả về có rất nhiều thông tin khác nhau, trong ứng dụng này ta chỉ cần đến các thông tin cơ bản như title, year, synopsis, posters.thumbnail, ratings.critics_scoreabridged_cast Class này có vai trò là 1 model của Box Office Video, ngoài việc nhận các biến đồng thời có hàm xử lý dữ liệu json và đưa dữ liệu vào các tham số tương ứng

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);
	}
    public static BoxOfficeMovie fromJson(JSONObject jsonObject) {
        BoxOfficeMovie b = new BoxOfficeMovie();
        try {
            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");
            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;
    }

    public static ArrayList<BoxOfficeMovie> fromJson(JSONArray jsonArray) {
        ArrayList<BoxOfficeMovie> movies = new ArrayList<BoxOfficeMovie>(jsonArray.length());
        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;
    }
}

Tạo adapter để nhập dữ liệu movie vào###

Sau khi đã lấy được dữ liệu từ API và gán vào model BoxOfficeVideo thì ta cần đưa từng video lấy được vào adapter rồi từ đó đưa vào lớp View. Đầu tiên cần khởi tạo adapter này:

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

Đồng thời ta tạo file View để thể hiện bố cục của từng movie trong app

<?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>

Sau khi có View rồi, tiếp theo là việc nhập dữ liệu vào từng id mà đã thiết lập ở file view trên


public class BoxOfficeMoviesAdapter extends ArrayAdapter<BoxOfficeMovie> {
    @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);
        }
        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);
                tvTitle.setText(movie.getTitle());
        tvCriticsScore.setText("Score: " + movie.getCriticsScore() + "%");
        tvCast.setText(movie.getCastList());
        Picasso.with(getContext()).load(movie.getPosterUrl()).into(ivPosterImage);
        return convertView;
    }

}

Ghép các phần vào với nhau###

Vậy sau khi ta lấy dữ liệu từ API, rồi đưa vào Model, sau đó đổ dữ liệu vào mảng. Và giờ ta tạo 1 class Activity để hợp nhất các thành phần với nhau lại.

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

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

Như vậy sau khi tạo xong class trên và chạy app, sẽ có kết quả hiển thị danh sách các phim chiếu trên rạp với các thông tin cơ bản như tên, ảnh, .... Bài viết trên tôi trình bày 1 cách cơ bản làm việc của ứng dụng Android với dữ liệu Api trả về dạng Json. Rất mong được sự đóng góp ý kiến của mọi người. Nguồn: http://guides.codepath.com/android/Rotten-Tomatoes-Networking-Tutorial


All Rights Reserved