Retrofit

Retrofit 是一套由 Square 所開發維護,將 RESTful API 寫法規範和模組化的函式庫。 底層也使用他們的 okHttp ,okHttp 用法參考 okHttp 章節

Retrofit 預設回傳的處理器是現代化 API 最流行的 JSON,如果你要處理別的要另外實作 Converter。

如果需要實作 Server 驗證,建議做好另外接上 okHttpClient 去設 Interceptor。在 Retrofit 1.9.0 的 Interceptor 中能做的有限。

限制

  1. Java 6(含) 和 Android 2.3(含) 以上

網路資源

Name Link
github https://github.com/square/retrofit
官網 http://square.github.io/retrofit/

安裝方式及 ProGuard 設定

請以 官網 為準

先備知識

繼續閱讀前以下東西要先知道。

  1. RESTful API:不知道的強烈建議先去了解一下。
  2. JSON:就格式要知道。
  3. Gson:稍微要了解一點用法。
  4. HTTP 的 GET、POST、PUT、DELETE 了解一下。

如何使用

這邊以寫作時最新的 Retrofit 1.9.0 版為準

常見用法

直接跳級來個難一點的吧~

建立你的 Server 的設定檔,通常都會有正式環境和測試環境和 API 版本。

public class Config {

    public enum Env {
        PROD("example.com"),
        DEV("dev.example.com");

        public final String host;
        Env(String host) {
            this.host = host;
        }
    }

    // API 要使用的 API 版本
    public final static int VERSION = 1;

    // 要去連的環境,這邊設定為正式環境
    public final static Env env = Env.PROD;

    public static String getEndPoint() {
        return getEndPoint(Config.env, VERSION);
    }

    public static String getEndPoint(Env env, int version) {
        return String.format("https://%s/api/v%d", env.host, version);
    }
}

以下用大家已經做到爛的論壇的文章和文章評論為例。 定義好和回傳 JSON 格式相符的 Geon Data Object

請無視重複的 article_id。 實際使用時,請做好封裝或是用 lombok

文章 Response 的 JSON 及對應的 class

{
    "id": 1,
    "user_id": 1,
    "content": "有地震",
    "created_at": "2015-03-11T20:36:42.000Z",
    "updated_at": "2015-04-27T09:06:56.000Z",
    "comments": [
        {
            "id": 1,
            "article_id": 1,
            "user_id": 2,
            "content": "頭推",
            "created_at": "2015-03-11T20:46:42.000Z",
            "updated_at": "2015-03-11T20:46:42.000Z"
        },
        {
            "id": 1,
            "article_id": 1,
            "user_id": 5,
            "content": "好快,輸了。",
            "created_at": "2015-03-11T20:57:31.000Z",
            "updated_at": "2015-03-11T20:57:31.000Z"
        },
    ]
}
public class Article {
    int id;

    @SerializedName("user_id")
    int userId;

    String content;

    @SerializedName("created_at")
    String createdAt;

    @SerializedName("updated_at")
    String updatedAt;

    @SerializedName("comments")
    List<Comment> comments = new ArrayList<Comment>();
}

文章評論 Response 的 JSON 及對應的 class

{
    "id": 1,
    "article_id": 1,
    "user_id": 2,
    "content": "頭推",
    "created_at": "2015-03-11T20:46:42.000Z",
    "updated_at": "2015-03-11T20:46:42.000Z"
}
public class Comment {
    int id;

    @SerializedName("article_id")
    int articleId;

    @SerializedName("user_id")
    int userId;

    String content;

    @SerializedName("created_at")
    String createdAt;

    @SerializedName("updated_at")
    String updatedAt;
}

定義與 API 符合的 Interface,這是 Retrofit 的最大賣點。

public interface ArticlesApi {
    String CONTENT = "content";

    //文章列表
    @GET("/articles")
    List<Article> index();

    //取得某篇文章
    @GET("/articles/{id}")
    Article show(
        @Path("id") int id
    );

    //新增文章
    @POST("/articles")
    @FormUrlEncoded
    Article create(
        @Field(CONTENT) String content
    );

    //編輯文章
    @PUT("/articles/{id}")
    @FormUrlEncoded
    Article update(
        @Path("id") int id,
        @Field(CONTENT) String content
    );

    //編輯文章
    @PUT("/articles/{id}")
    Article update(
        @Path("id") int id,
        @Body Article article
    );

    //刪除文章
    @DELETE("/articles/{id}")
    Response delete(@Path("id") int id);
}
public interface CommentsApi {
    String CONTENT = "content";

    //取得某篇文章的評論
    @GET("/articles/{article_id}/comments")
    @FormUrlEncoded
    List<Comment> index(
        @Path("article_id") int articleId,
        @Field(CONTENT) String comment
    );

    //新增評論
    @POST("/articles/{article_id}/comments")
    @FormUrlEncoded
    Comment create(
            @Path("article_id") int articleId,
            @Field(CONTENT) String content
    );

    //新增評論
    @POST("/articles/{article_id}/comments")
    Comment create(
        @Path("article_id") int articleId,
        @Body Comment comment
    );

    //編輯評論
    @PUT("/comments/{id}")
    @FormUrlEncoded
    Comment update(
        @Path("id") int id,
        @Field(CONTENT) String content
    );

    //編輯評論
    @PUT("/comments/{id}")
    Comment update(
        @Path("id") int id,
        @Body Comment comment
    );

    //刪除評論
    @DELETE("/comments/{id}")
    Response delete(@Path("id") int id);
}

建立負責處理 API 的 class

public class ApiClient {

    private final RestAdapter restAdapter;
    private final ArticlesApi articlesApi;
    private final CommentsApi commentsApi;

    public ApiClient() {
        //這邊的使用情境為全部 API 都對同一個 Server 操作
        restAdapter = new RestAdapter.Builder()
                .setEndpoint(Config.getEndPoint())
                .build();

        articlesApi = restAdapter.create(ArticlesApi.class);
        commentsApi = restAdapter.create(CommentsApi.class);
    }

    /*
     * 簡單示範幾個方法,實際使用可在這裡作內容驗證
     */

    public Article showArticle(int id) {
        return articlesApi.show(id);
    }

    public Comment createComment(int articleId, String content) {
        return commentsApi.create(articleId, content);
    }

    //只能透過傳入 Comment 物件刪除,避免誤傳如文章 id 之類的
    public boolean deleteComment(Comment comment) {
        return deleteComment(comment.id);
    }

    //將根據 id 刪除評論的方法封裝起來
    private boolean deleteComment(int id) {
        Response response = commentsApi.delete(id);
        final int statusCode = response.getStatus();

        return 200 <= statusCode && statusCode < 300;
    }
}

Sign Request

如果 API 需要做 sigunature,在 Retrofit 的 Interceptor 無法做到,要抽換掉Client 改用 okHttp 的 Interceptor 才可以。 用法參考 okHttp#Interceptor

public class ApiClient {

    private final RestAdapter restAdapter;

    private final OkHttpClient client;

    private final Interceptor signRequestInterceptor = new SignRequestInterceptor(); 

    public ApiClient() {
        client = new OkHttpClient();
        client.networkInterceptors().add(signRequestInterceptor);

        OkClient retrofitClient = new OkClient(client);

        restAdapter = new RestAdapter.Builder()
            .setEndpoint(Config.getEndPoint())
            .setClient(retrofitClient)
            .build();
    }
    ...
}

Handle Structured JSON

API 回傳具有格式的 JSON 時,請參考 GSON 部分的 Handle Structured JSON

將設定好的 Gson 放到 GsonConverter 內即可。用法如下:

public class ApiClient {

    private final RestAdapter restAdapter;

    public ApiClient() {
        restAdapter = new RestAdapter.Builder()
            .setEndpoint(Config.getEndPoint())
            .setConverter(new GsonConverter(new GsonBuilder()
                .registerAdapterFactory(new ItemAdapterFactory())
                .setDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" )
                .create();
            ))
            .build();
    }
}

Share GSON Model With Realm Database Model

如果你的 Database 剛好使用 Realm 並且想共用你的 GSON API Model

詳細請見 GSON 的 Share GSON Model With Realm Database Model

public class ApiClient {

    private final RestAdapter restAdapter;

    public ApiClient() {
        restAdapter = new RestAdapter.Builder()
            .setEndpoint(Config.getEndPoint())
            .setConverter(new GsonConverter(new GsonBuilder()
                .setExclusionStrategies(new RealmExclusionStrategy())
            .create();
        ))
        .build();
    }
}

附錄

Cheat Sheet

最常見的 4 種 HTTP Request (GET、POST、PUT、DELETE) 及相關變化在 Retrofit 內的寫法。

GET

@GET("/articles")
List<Article> index();

Request 路徑由參數組成,使用中括號{``}@Path

@GET("/users/{id}")
User show(
    @Path("id") int id
);

GET 加上 query string,使用@Query

@GET("/search")
List<Place> search(
    @Query("query") String query,

    @Query("north") Float boundNorth,
    @Query("south") Float boundSouth,
    @Query("west") Float boundWest,
    @Query("east") Float boundEast,

    @Query("order") String sortKey,
    @Query("asc") String ascOrDesc
);

POST or PUT 靠 GSON 幫忙 Serailize 來送出 JSON 資料,POST 和 PUT 使用@Body指定 POST Body

@POST("/articles/{article_id}/comments")
Comment create(
    @Path("article_id") int articleId,
    @Body Comment comment
);

POST or PUT 自己組的 JSON String

@POST("/articles/{article_id}/comments")
Comment create(
    @Path("article_id") int articleId,
    @Body TypedInput comment
);
String json = "{\"rating\":3,\"content\":\"my content...\"}";
TypedInput in = new TypedByteArray("application/json", json.getBytes("UTF-8"));

POST or PUT Url encode 過的表單資料,用@FormUrlEncoded,使用@Field個別指定

@POST("/articles/{article_id}/comments")
@FormUrlEncoded
Comment create(
    @Path("article_id") int articleId,
    @Field("rating") int rating,
    @Field("content") String content
);

POST or PUT Url encode 過的表單資料,用@FormUrlEncoded,參數也可用@Body

@POST("/articles/{article_id}/comments")
@FormUrlEncoded
Comment create(
    @Path("article_id") int articleId,
    @Body Comment comment
);

POST or PUT 用@Multipart來上傳檔案

@PUT("/me")
@Multipart
Me update(
    @Part("display_name") String displayName,
    @Part("avatar") TypedFile avatar
);
File file = ...
TypedFile avatar =  new TypedFile("multipart/form-data", file);

DELETE

@DELETE("/comments/{id}")
Response delete(
    @Path("id") int id
)

results matching ""

    No results matching ""