この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは。Steam厨のこむろ@札幌です。ここ最近はPAYDAY2です。 *1
お盆を過ぎたら札幌はめっきり朝夕冷え込むようになりました。本当にエアコンはいらない土地でした(驚愕
今回は、弊社諏訪ネ申のRxAndroid+Retrofitの記事 にインスパイアされて書きました。
はじめに
ChatworkのREST APIをコールするAndroidアプリケーションを作成します。今回は、GET
と POST
を実装してみます。
Retrofit + Kotlin
基本的にはJavaの時とあまり変わりませんが、大きく違うことがあります。 null
を許容する値を大幅に駆逐できることです。
Retrofitを利用する際には、レスポンスJSONをマッピングするためのオブジェクトが必要になります。Kotlinでは以下のように記述することが出来ました。 破壊的代入を可能にする var
や null
を許容する ?
も書かずにオブジェクトを定義することができました。
この記事では、 チャットルーム一覧の取得 と メッセージ投稿 のAPIをコールするアプリを作成してみます。
REST APIを実行するための準備
Retrofitを利用してREST APIの呼び出しを定義するための準備をしましょう。
RestAdapterの作成
処理を呼び出したい箇所でRetrofitのRestAdapterを作成します。この辺りの実装は こちら を参考にしています。JsonParserにはGsonを利用します。
// chatwork API endpoint
private val ENDPOINT = "https://api.chatwork.com/v1"
val gsonBuilder = GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(javaClass(), DateTypeAdapter()).create()
val restAdapter = RestAdapter.Builder()
.setEndpoint(ENDPOINT)
.setConverter(GsonConverter(gsonBuilder))
.setLogLevel(RestAdapter.LogLevel.FULL)
.setLog(AndroidLog("=NETWORK="))
.build();
特に認証など必要ないAPIであればこれだけで問題ありません。 今回は、Chatwork APIを対象にしているため、認証情報をヘッダに埋め込む必要があります。
API TOKENをヘッダに埋め込む
ヘッダ情報に情報を追加する場合は、Retrofitでは RequestInterceptor
を利用します。
今回の場合はChatwork APIのAPI TOKENを埋め込むので以下のように記述します。
private val API_KEY = ""
private val TOKEN_KEY = "X-ChatWorkToken"
val requestInterceptor: RequestInterceptor = object: RequestInterceptor {
override fun intercept(request: RequestInterceptor.RequestFacade?) {
request?.let {
it.addHeader(TOKEN_KEY, API_KEY)
}
}
}
RequestInterceptor
にAPI TOKENを追加したら、先ほど定義したRestAdapterにセットします。
val restAdapter = RestAdapter.Builder()
.setEndpoint(ENDPOINT)
.setRequestInterceptor(requestInterceptor)
.setConverter(GsonConverter(gsonBuilder))
.setLogLevel(RestAdapter.LogLevel.FULL)
.setLog(AndroidLog("=NETWORK="))
.build();
これでChatwork APIをコールするための準備が整いました。
チャットルーム一覧を取得する
自分のチャット一覧取得 を確認してみるとAPIを GET
で引数なしで呼び出す必要があるようです。1つずつ準備をしていきましょう。
レスポンスをマッピングするオブジェクトを定義する
ChatRoom一覧APIは以下の様なJSONがレスポンスとして返却されます。
[
{
"room_id": 123,
"name": "Group Chat Name",
"type": "group",
"role": "admin",
"sticky": false,
"unread_num": 10,
"mention_num": 1,
"mytask_num": 0,
"message_num": 122,
"file_num": 10,
"task_num": 17,
"icon_path": "https://example.com/ico_group.png",
"last_update_time": 1298905200
}
]
配列になっているオブジェクトをマッピングするための RoomEntityクラス を作成します。
public class RoomEntity(roomId: Int, name: String,
type: String, role: String,
sticky: Boolean,
unreadNum: Int,
mentionNum: Int,
mytaskNum: Int,
messageNum: Int,
fileNum: Int,
taskNum: Int,
iconPath: String,
lastUpdateTime: Long) {
public val roomId: Int = roomId
public val name: String = name
public val type: String = type
public val role:String = role
public val sticky: Boolean = sticky
public val unreadNum: Int = unreadNum
public val mentionNum: Int = mentionNum
public val mytaskNum: Int = mytaskNum
public val messageNum: Int = messageNum
public val fileNum: Int = fileNum
public val taskNum: Int = taskNum
public val iconPath: String = iconPath
public val lastUpdateTime: Long = lastUpdateTime
}
全て null
を許可しない値で定義してあるので、レスポンスを取得した後の処理では特にnullチェックをすることなく利用することが出来ます。
インターフェースの作成
続いてAPI呼び出しのインターフェースを作成します。
/**
* Created by komurohiraku on 2015/07/12.
*/
public interface ChatworkService {
@GET("/rooms")
public fun getRooms(): Observable<Array>
}
https://api.chatwork.com/v1/rooms
というパスに対して GET
を実行した場合の動作は getRooms()
という名前でチャットルーム一覧を取得するメソッドとして定義します。
返却値には先ほど作成した RoomEntity
の配列を Observable
に包んで指定します。 *2
チャットルーム一覧取得を実行する
表示する Fragment
内で チャットルーム一覧取得 を呼び出す実装を行います。
restAdapter.create(javaClass())
.getRooms()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object:Observer<Array>{
override fun onCompleted() {
// 正常に完了した場合
}
override fun onError(e: Throwable?) {
// エラーの場合
}
override fun onNext(t: Array?) {
// 受信した結果
}
})
これでまずはチャットルーム一覧のAPIを呼び出すことができるようになりました。
チャットルーム一覧の結果を加工する
結果のレスポンスは override fun onNext(t: Array?)
で取得することが出来ます。
Chatwork APIのチャット一覧のレスポンスを確認すると分かるのですが、レスポンスにはチャットルームである グループ と 個人 が混ざっています。今回は、チャットルームの一覧を取得したいので、 グループ のみをフィルタして結果としましょう。
override fun onNext(t: Array?) {
t?.let {
val groups = it.filter {
room -> room.type.equals("group")
}
// ランダムで一つの部屋を選択して通知
val index = Random().nextInt() * 100 % groups.size()
val room = groups.get(Math.abs(index))
Toast.makeText(getActivity(), room.name + "が選択されました", Toast.LENGTH_SHORT).show()
}
}
Kotlinの Array
の filter
を利用して type
が "group" であるもののみを抽出します。抽出した結果もまた Array
です。
チャットルーム一覧取得の機能はこれで概ね完成。全体のコードは以下の通り。
restAdapter.create(javaClass())
.getRooms()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object:Observer<Array>{
override fun onCompleted() {
// 正常に完了した場合
Snackbar.make(getView(), "ネコと和解せよ!", Snackbar.LENGTH_SHORT).show()
}
override fun onError(e: Throwable?) {
// エラーの場合
e?.printStackTrace()
}
override fun onNext(t: Array?) {
// 受信した結果
t?.let {
val groups = it.filter { rooms -> rooms.type.equals("group") }
val index = Random().nextInt() * 100 % groups.size()
val room = groups.get(Math.abs(index))
Toast.makeText(getActivity(), room.name + "が選択されました", Toast.LENGTH_SHORT).show()
}
}
})
以上がチャットルーム一覧を取得する方法。
メッセージを投稿する
メッセージを投稿する機能を実装します。チャットに新しいメッセージを追加 こちらを確認すると、APIを POST
で呼び出し引数にメッセージとなる文字列を FormUrlEncode
に設定すれば良いようです。さっそく作っていきましょう。 *3
レスポンスをマッピングするオブジェクトを定義する
特定のチャットルームにメッセージを投稿する際のレスポンスは、メッセージIDのみが返却されます。
{
"message_id": 1234
}
そのため、レスポンスオブジェクトは ResponseEntity
として以下のように定義します。
public class ResponseEntity(messageId: Int) {
public val messageId:Int = messageId
}
messageId
のみで事足りるため特に問題はないでしょう。 *4
インターフェースの作成
メッセージを投稿する機能のインターフェースを作成します。
@FormUrlEncoded
@POST("/rooms/{room_id}/messages")
public fun postMessage(@Path("room_id") roomId:Int, @Field("body") message: String): Observable
@POST("/rooms/{room_id}/messages")
パスの中に {}
があります。このように記述するとメソッドの引数を使い、任意の値を埋め込む事ができます。
メソッドの引数でPathに任意の値を埋め込む
@Path("room_id") roomId:Int
この引数で指定された値がPathに埋め込まれて実行されます。
URLエンコードを指定する
@FormUrlEncoded
このアノテーションの指定と @Field("body")
を Body で指定された文字列に対して必要になります。
メッセージ投稿を実行する
先ほどと同じく Fragment
内に実装してみます。呼び出し方は先程のチャットルーム一覧取得と変わりません。異なるのは引数に情報が必要であることくらいでしょうか。
private fun postMessage(roomId:Int, message:String, observer: Observer): Subscription {
return restAdapter.create(javaClass())
.postMessage(roomId, message)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer)
}
一つのプライベートメソッドとして定義してみます。呼び出し方は以下の通り。
postMessage(13768115, "ネコと和解せよ!", object:Observer {
override fun onCompleted() {
// 正常に完了した場合
Toast.makeText(getActivity(), "投稿されましたにゃ", Toast.LENGTH_SHORT).show()
}
override fun onNext(t: ResponseEntity?) {
// レスポンスを受信
t?.let {
Log.d("PieceNeko", "MessageID : " + it.messageId)
}
}
override fun onError(e: Throwable?) {
// エラーの場合
e?.printStackTrace()
}
})
実行結果
実行した結果はこんな感じ
まとめ
Retrofitが便利そうだったのでKotlinで使ってみました。Kotlinらしいコードには程遠いですが、問題なく直感的にサクサク書けます。通信の結果を加工するあたりでは、Kotlinが大いに活躍できるのではないでしょうか?(RxAndroidと機能被る部分もありそうではありますが)
次はこれらの機能を組み合わせてみます。