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よりコード量少なくて楽で良い