はじめに
FiNCでAndroidのアプリ開発を担当している南里、通称Bisonと申します。
テーマは開発プロセスなどを含めて何にするか悩んだんですが、やっぱりAndroid周りの技術のお話をしようかなと思います。
デシリアライズ(シリアライズ)
以下のようなリソースBisonを考えてみましょう。
{
"id": 1,
"name": "南里勇気",
"age": 26
}
Android開発をしているみなさんにはおなじみですが、
HTTP通信でサーバーからデータ(リソース)を取得 -> jsonをデシリアライズ -> JavaのObject(POJO)を生成
という場面が多々あるかと思います。(別にAndroidだけってことではないですが)
Androidだと、シリアライズ、デシリアライズのサードパーティライブラリとしては、Gson などでしょうか?
最近の流行りでいうと、
RxJava, Retrofit などを利用しますよね。
そこで本日は、
・RxJava × Retrofit2のHttp Clientの作成
・リソースのデシリアライズ(シリアライズ)方法
について簡単にまとめます。
RxJava × Retrofit2 Http Clientの作成
Steps
- Retrofit Clientを作成する
- Serviceの作成。(Observable)
1. Retrofit Clientを作成する
まず、Retrofitとは、Squareで作成されたAndroid(Java)のためのHttp Client ライブラリのことです。
(SquareはAndroid業界では圧倒的戦闘力をもつJake Whartonが所属しています。)
まずRetrofit Clientを作成します。
(余談ですが、Retrofit -> Retrofit2へのアップグレードは相当breakingでしたね)
内部はBuilderパターンで実装されており、
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.bison.com")
.client(okhttpClient);
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
こんな感じで簡単に実装できます。
RxJavaを利用するためには、.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
この一行が必要になるので、お忘れなく。
また、Httpのheader情報、その他のオプション情報をセットしたい場合には、Okhttp3のInterceptorという仕組みを利用します。
this.headerInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder newBuilder = request.newBuilder();
newBuilder.addHeader("Client-Type", "bison");
final Request newRequest = newBuilder.build();
return chain.proceed(newRequest);
}
};
Okhttp Clientに上記をセットして使ってください。
2. Serviceの作成。(Observable)
上記まで完了すれば、あとはend pointを設定しましょう。
詳しくは、この辺り読んでみてください。
http://square.github.io/retrofit/2.x/retrofit/retrofit2/http/GET.html
public interface BisonApiService {
// HTTPリクエストのメソッド、エンドポイントのセット
@GET("v1/bisons/{bison_id}")
Observable<Bison> getBison(@Path("bison_id") int bisonId);
}
あとは、
retrofit.create(BisonApiService.class)
.getBison(100);
とすれば、interfaceに定義した、 Observable<Bison>
が取得できます。
あとはsubscribeして、Bisonモデルをのデータを取得すれば、完了です。
RxJavaを利用したObservableの扱いは非同期や並列処理にも役立つRxJavaの使い方などがとても参考になるかと思います。
次は、Bisonのデータの取得に関してです。
ここから本題のデシリアライズに入りましょう。
a. Gsonの設定
b. APIのレスポンスをMappingするModelを作成する。
a. デシリアライズしたい際には、以下のように別モジュールにあるGsonConverterをセットします。
Gson gsonInstance = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gsonInstance));
b. APIのレスポンス用モデルの作成(シリアライズ、デシリアライズ両用)
public class Bison{
@SerializedName("id")
public final int id;
@SerializedName("name")
public final String name;
@SerializedName("age")
public final int age;
// コンストラクタ
}
これでGsonが上手にデシリアライズ、シリアライズ(post, patchなどもOK)してくれます。
ただ、実際に開発をしていると次のような問題にぶち当たると思います。
例えば、以下のようにデータ構造に差異がある場合です。
{
"page": "1",
"data": [
{
"type": "bison",
"id": 1,
"name": "南里勇気",
"age": 26
},
{
"type": "snake",
"id": 3,
"name": "viper"
}
]
}
上記のパターンは、ページングを行うタイムライン系のサービスではよくあります。
その場合、Observable<List<特定のクラス>>
みたいにしたくなりますよね?
蛇もバイソンもアニマルなので、Animalクラスを定義して継承して、 Observable<List<Animal>>
にしたくなります。
ところが、それぞれの具象クラス(Bison, Snake)にデシリアライズしなければ、上記のようにList のデータを有効活用できません。
なので、具象クラスにパースする必要があります。
その場合には、GsonのJsonDeserializerを実装したカスタムクラスを作成します。
public class AnimalDeserializer
implements JsonDeserializer<Animal> {
@Override
public Animal deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
final JsonElement jsonActivityType = jsonObject.get("type");
// 上記のキー("type")はアプリケーション内で一意性を保つ必要があります。(通信の際には常にこのDeserializerがregistされているため)
final String type = jsonActivityType.getAsString();
switch (type) {
case "bison":
return context.deserialize(json, Bison.class);
case "snake":
return context.deserialize(json, Snake.class);
default:
return context.deserialize(json, Animal.class);
}
}
}
あとは、上記でも記載しているGsonBuilderに部分で、以下を追加すると完成です。
new GsonBuilder().registerTypeAdapter(Animal.class, new AnimalDeserializer());
これで、RxJava × Retrofit2での適切なデシリアライズ(シリアライズ)が可能になりました。
軽く調べた感じでは、あまり検索にヒットしなかったので、一人でも多くのAndroidエンジニアを救えたらと願うそんな12/7なのでした。