いつもAndroidでRetrofit+RxJavaを使ってAPI通信する処理を書いてるが、今回はKotlinで挑戦してみる。
ついでに、使ったことのないnowもJacksonも触ってみた。
作成するもの
- nowを使ったnodejs製のAPIモック(express)
- Androidアプリ(Kotlin/Retrofit/RxJava/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/ikemura23/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')"
}
kotlin: PostResponse
/**
* 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/ikemura23/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よりコード量少なくて楽で良い