Android Library: Tìm hiểu Retrofit 2.0

retrofit_banner.png

Giới thiệu

Trước giờ trên Android có nhiều cách để kết nối và lấy dữ liệu từ 1 WebService. Có thể bạn đã từng dùng các lớp trong gói Http Apache (Loại bỏ trên Android 6.0, phải dùng như là 1 thư viện ngoài) hoặc đã từng dùng 1 số thư viện để kết nối với internet và nhận các dữ liệu từ server như Volley (Google), KSOAP ...

Hôm nay tôi xin giới thiệu một thư viện vô cùng lợi ích cho việc kết nối internet và nhận dữ liệu từ server một cách dễ dàng và viết code theo mô hình chuẩn RESTFul Webservices đó là:

Retrofit (newest: Ver2.0 beta)

Retrofit là một Rest Client (Tìm hiểu thêm về chuẩn RESTFul dưới link tham khảo) cho Android và Java và được tạo ra bởi Square. Họ làm cho việc nhận và tải lên JSON (hoặc dữ liệu khác) một cách khá dễ dàng tới một WebService dựa trên mô hình REST.

Các gói trang bị thêm cho phép sử dụng các bộ chuyển đổi sau đây:

  • Gson: com.squareup.retrofit:converter-gson

  • Jackson: com.squareup.retrofit:converter-jackson

  • Moshi: com.squareup.retrofit:converter-moshi

  • Protobuf: com.squareup.retrofit:converter-protobuf

  • Wire: com.squareup.retrofit:converter-wire

  • Simple XML: com.squareup.retrofit:converter-simplexml

Hướng dẫn sử dụng

Để làm việc với Retrofit bạn cần triển khai cơ bản 3 lớp:

  1. Model class to map JSON Data

  2. Interfaces để định nghĩa các API cho Webservice

  3. Retrofit.Builder Lớp để định nghĩa URL Endpoint cho các hoạt động liên quan tới Http

Retrofit có thể sử dụng cho các ứng dụng Java nhưng trong hướng dẫn này tôi demo nó trên một ứng dụng Android (Sử dụng Official IDE: Android Studio).

Yêu cầu: Sử dụng Retrofit để lấy dữ liệu từ Stackoverflow.

Bạn có thể xây dựng các liên kết để lấy dữ liệu từ Stackexchange docs side.

Sau khi lựa chọn tôi có liên kết để lấy danh sách câu hỏi từ tag Android trên Stackoverflow với liên kết như sau:

https://api.stackexchange.com/2.2/search?order=desc&sort=activity&tagged=Android&site=stackoverflow

Mẫu JSON trả về:

{
  "items": [
    {
      "tags": [
        "android",
        "android-studio"
      ],
      "owner": {
        "reputation": 706,
        "user_id": 5139222,
        "user_type": "registered",
        "accept_rate": 100,
        "profile_image": "https://i.stack.imgur.com/g8Gvu.jpg?s=128&g=1",
        "display_name": "KuKeC",
        "link": "http://stackoverflow.com/users/5139222/kukec"
      },
      "is_answered": false,
      "view_count": 4,
      "answer_count": 0,
      "score": 0,
      "last_activity_date": 1448553737,
      "creation_date": 1448553737,
      "question_id": 33942820,
      "link": "http://stackoverflow.com/questions/33942820/android-studio-doesnt-recognize-cat-b15q",
      "title": "Android studio doesn't recognize Cat B15Q"
    }
  ]
  "has_more": true,
  "quota_max": 10000,
  "quota_remaining": 9983
}

I, Tạo project và cài đặt Retrofit

Tạo 1 project với package tuỳ ý, ở đây tôi đặt tên package:

com.demo.retrofit.stackoverflow

Đưa cài đặt thử viện Retrofit tới dependencies trong file build.gradle (Module)

compile 'com.squareup.retrofit:retrofit:2.0.0-beta2'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'

Tiếp theo đưa thêm permission để kết nối Internet tới AndroidManifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.demo.retrofit.stackoverflow" >

    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

II, Tạo các lớp

Trong việc nhận về JSON từ StackOverflow chúng ta chỉ cần quan tâm tới 2 giá trị của Title và Link. Do đó chúng ta tạo ra lớp dữ liệu sau đây:

package android.vogella.com.retrofitstackoverflow;

// Lớp này sử dụng để map dữ liệu giữa các keys trong JSON tới Object sử dụng GSON
public class Question {

    String title;
    String link;

    @Override
    public String toString() {
        return(title);
    }
}
package com.demo.retrofit.stackoverflow;

import java.util.List;
// Lớp nay để lấy về danh sách question (node: items trong JSON trả về)
public class StackOverflowQuestions {
    List<Question> items;
}

Định nghĩa các REST API (Api Services) cho Retrofit

package com.vogella.android.retrofitstackoverflow;

import retrofit.Callback;
import retrofit.http.GET;
import retrofit.http.Query;
import retrofit.Call;

public interface StackOverflowAPI {
    @GET("/2.2/questions?order=desc&sort=creation&site=stackoverflow")
    Call<StackOverflowQuestions> loadQuestions(@Query("tagged") String tags);
}

Tạo 1 Menu như đưới đây để gọi dữ liệu từ server về, đặt tên main_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/menu_load"
        android:title="Load Data" />
</menu>

Thay đổi lớp MainActivity.class để cho phép lấy dữ liệu từ StackOverFlow với tag là 'Android'

package com.demo.retrofit.stackoverflow;

import android.app.Activity;
import android.app.ListActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.Toast;

import java.util.ArrayList;

import retrofit.Call;
import retrofit.Callback;
import retrofit.GsonConverterFactory;
import retrofit.Response;
import retrofit.Retrofit;

/* Dữ liệu được fill lên ListView vì vậy kế thừa từ ListActivity để làm việc với List nhanh chóng hơn.
Implement interface Callback để triển khai các callback cho việc xử lý Http thành công hay thất bại */
public class MainActivity extends ListActivity implements Callback<StackOverflowQuestions> {

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Hiển thị title loading
        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
        requestWindowFeature(Window.FEATURE_PROGRESS);

        // Set Adapter mặc định cho Activity
        ArrayAdapter<Question> arrayAdapter =
                new ArrayAdapter<Question>(this,
                        android.R.layout.simple_list_item_1,
                        android.R.id.text1,
                        new ArrayList<Question>());
        setListAdapter(arrayAdapter);

        // Visible loading
        setProgressBarIndeterminateVisibility(true);
        setProgressBarVisibility(true);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Gán Menu cho Activity
        getMenuInflater().inflate(R.menu.main_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Khi lựa chọn Load Data từ Menu
        setProgressBarIndeterminateVisibility(true);

        // Khởi tạo Retrofit để gán API ENDPOINT (Domain URL) cho Retrofit 2.0
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://api.stackexchange.com")
                // Sử dụng GSON cho việc parse và maps JSON data tới Object
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        // Khởi tạo các cuộc gọi cho Retrofit 2.0
        StackOverflowAPI stackOverflowAPI = retrofit.create(StackOverflowAPI.class);

        Call<StackOverflowQuestions> call = stackOverflowAPI.loadQuestions("android");
        // Cuộc gọi bất đồng bọ (chạy dưới background)
        call.enqueue(this);

        // Nếu bạn muốn chạy đồng bồ trên main thread sử dụng phương thức execute
        // call.execute()

        // Để Cancel request:
        // call.cancel();

        // Có thể clone một cuộc gọi trước đó
        //Call<StackOverflowQuestions> c = call.clone();
        //c.enqueue(this);

        return true;
    }

   // Phương thức được triển khai khi implement interface  Callback
    @Override
    public void onResponse(Response<StackOverflowQuestions> response, Retrofit retrofit) {
        // Visible Loading bar
        setProgressBarIndeterminateVisibility(false);

        // Gán giá trị cho Adapter để fill tới List
        ArrayAdapter<Question> adapter = (ArrayAdapter<Question>) getListAdapter();
        adapter.clear();
        // Lấy dữ liệu trả về từ Response qua Body()
        adapter.addAll(response.body().items);
    }

    @Override
    public void onFailure(Throwable t) {
        // Khi có vấn đề từ Server như các lỗi 4xx 5xx ....
        Toast.makeText(MainActivity.this, t.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
    }
}

Mở rộng

Bạn cũng có thể custom cho các giá trị trả về từ Server theo 1 mẫu nào đó mình mong muốn. Trường hợp thường gặp nhất đó là server trả về các giá trị success là 0 hoặc 1. Mình muốn chuyển đổi chúng thành các giá trị Boolean True hay False cho các Model được ánh xạ. Triển khai các lớp như sau để thông báo việc custom này với GSON:

package com.demo.retrofit.stackoverflow;

import java.lang.reflect.Type;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

public class BooleanSerializer implements JsonSerializer<Boolean>, JsonDeserializer<Boolean> {

    @Override
    public JsonElement serialize(Boolean arg0, Type arg1, JsonSerializationContext arg2) {
        return new JsonPrimitive(arg0 ? 1 : 0);
    }

    @Override
    public Boolean deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2) throws JsonParseException {
        return arg0.getAsInt() == 1;
    }
}

Và đăng ký với GSON về việc custom này

   BooleanSerializer serializer = new BooleanSerializer();
   GsonBuilder b = new GsonBuilder();
   b.registerTypeAdapter(Boolean.class, serializer);
   b.registerTypeAdapter(boolean.class, serializer);
   Gson gson = b.create();

   Retrofit retrofit = new Retrofit.Builder()
         .baseUrl("https://api.stackexchange.com")
         // Sử dụng GSON cho việc parse và maps JSON data tới Object
         .addConverterFactory(GsonConverterFactory.create(gson))
         .build();

Kết Luận

Sử dụng Retrofit thực sự chuyên nghiệp khi bạn thực hiện các request một cách dễ dàng, việc theo chuẩn mô hình RESTFul cho các API Service khiến code rất clear và dễ maintain. Việc thực hiện các kết nối Http Retrofit sử dụng mặc định OkHttp một Lib cũng của Square và được triển khai trong các SDK rất nổi tiếng như: Facebook SDK, Fabric (Twitter) ....

Tham khảo

Retrofit:

RESTFul Webserices