nowで作成したAPIをKotlinとRetrofitとRxJavaとJacksonでGET/POSTする
いつもAndroidでRetrofit+RxJavaを使ってAPI通信する処理を書いてるが、今回はKotlinで挑戦してみる。
ついでに、使ったことのないnowもJacksonも触ってみた。
作成するもの
環境
- macOS Sierra
- kotlin_version = '1.2.21'
- Android Studio 3.0.1
- node v9.4.0
API
準備
APIモックをexpressを使ったnodejsで作成し、nowにデプロイしていく。
nowについてはこの記事を参考にした。
以下、作業ログ
// APIモックのプロジェクト準備 $ mkdir try-now && cd try-now // package.json作成 $ npm init -y // expressを追加 $ npm i -S express // apiの処理を書くjsを作成 $ touch index.js // nowのインストール $ npm i -G now // nowのログイン(メールアドレスを入力すると確認メールが届くのでverifyする) $ now login
実装
index.jsに以下を貼り付ける。
レスポンス変数は適当に作った。
var express = require('express') var app = express() app.get('/', function (req, res) { const json = { status: 1, card_status: 10, card_type: 'normal', card_id: "ca1111", } res.json(json) }) app.post('/', function (req, res) { const json = { status: 2, card_status: 20, result_code: 1 } res.json(json) }) app.listen(3000)
ポートは3000を指定してるけど、なくても良い。
これでAPIモックはできあがった。
$ node index.js
としてローカルでも使えるが、今回はnowにデプロイする
デプロイ
nowでデプロイ、超簡単
$ now
https://try-now-kplvmhijbn.now.sh
というアドレスが表示されたので、これをAPIモックとして利用できる。
このアドレスに対してgetやpostをしていく。
Androidアプリ
使用するライブラリ
app/build.gradleに以下のライブラリ達を追加する
// Retrofit2 implementation 'com.squareup.retrofit2:retrofit:2.3.0' implementation 'com.squareup.retrofit2:converter-jackson:2.3.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' // RxJava2 implementation "io.reactivex.rxjava2:rxjava:2.1.9" // RxAndroid implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' // Jackson implementation 'com.fasterxml.jackson.core:jackson-core:2.9.4' implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.4' implementation 'com.fasterxml.jackson.core:jackson-annotations:2.9.4' implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.4"
ソース:https://github.com/banbara23/try-now
実装
Retrofitのインタフェースを作成。
戻り値はRxjavaのObservableにする。
interface ApiService { @POST("/") fun post(): Observable<PostResponse> @GET("/") fun get(): Observable<GetResponse> }
APIクライアントを作成していく。
今回、baseUrlにはnowで作成したurlを設定する。
class ApiClient { var apiService: ApiService init { val retrofit: Retrofit = Retrofit.Builder() .baseUrl("https://try-now-kplvmhijbn.now.sh") .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(JacksonConverterFactory.create(ObjectMapper())) .build() apiService = retrofit.create(ApiService::class.java) } /** * GETリクエスト */ fun get(): Observable<GetResponse> = apiService.get() /** * POSTリクエスト */ fun post(): Observable<PostResponse> = apiService.post() }
APIクライアント呼び出しは以下
ラムダ初めて触った、すごい
// get apiClient.post() .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe( { res -> Log.d(TAG, res.toString()) }, { error -> Log.e(TAG, "{$error.message}") }, { Log.d(TAG, "post completed") } ) // post apiClient.post() .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe( { res -> Log.d(TAG, res.toString()) }, { error -> Log.e(TAG, "{$error.message}") }, { Log.d(TAG, "post completed") } )
モデルについては、 Response(共通レスポンス)を継承した各モデルが存在するイメージで作成した。
/** * 共通レスポンス */ open class Response( @field:JsonProperty("status") val status: Int = 0, @field:JsonProperty("card_status") val cardStatus: Int = 0 )
/** * GETレスポンスのモデル */ data class GetResponse( @field:JsonProperty("card_type") val cardType: String = "", @field:JsonProperty("card_id") val cardId: String = "" ) : Response() { override fun toString(): String = "GetResponse(status='$status' cardStatus='$cardStatus' cardType='$cardType', cardId='$cardId')" }
/** * POSTレスポンスのモデル */ data class PostResponse( @field:JsonProperty("card_type") val cardType: String = "", @field:JsonProperty("card_id") val cardId: String = "", @field:JsonProperty("result_code") val resultCode: Int = 0 ) : Response() { override fun toString(): String = "PostResponse(status='$status' cardStatus='$cardStatus' resultCode='$resultCode')" }
この継承してるあたり、もうちょいうまくできないだろうか。
ビルドして実際に動作させると、Logcatに以下のログが表示されていればGET/POSTが成功している
getのログ D/AppCompatActivity: GetResponse(status='1' cardStatus='10' cardType='normal', cardId='ca1111') D/AppCompatActivity: get completed postのログ D/AppCompatActivity: PostResponse(status='2' cardStatus='20' resultCode='1') D/AppCompatActivity: post completed
ソース:https://github.com/banbara23/Kotlin-Jackson-Sample
ハマったところ
Jacksonが@JsonPropertyを認識してくれない
APIレスポンスをJacksonでシリアライズする際、共通レスポンスモデルであるResponse.ktがKotlin用のアノテーションでないとエラーになる。
Jacksonが継承元の変数を@JsonPropertyだと認識してくれなかった。
継承先は@JsonPropertyでも大丈夫なのに…
NG : @JsonProperty
OK : @field:JsonProperty
実際のコード
/** * 共通レスポンス */ open class Response( // NG //@JsonProperty("status") val status: Int = 0, //@JsonProperty("card_status") val cardStatus: Int = 0 // OK @field:JsonProperty("status") val status: Int = 0, @field:JsonProperty("card_status") val cardStatus: Int = 0 )
同様の問題が起きてないか調べたところ、以下のリンクに辿り着き、kotlinアノテーションを使うことで解決できた。 - https://github.com/FasterXML/jackson-module-kotlin/issues/56#issuecomment-321932309 - https://github.com/FasterXML/jackson-module-kotlin/issues/98#issuecomment-352016501
感想
nowが楽すぎてすごい
kotlinだとJavaよりコード量少なくて楽で良い