Edited at

【Android】Routerクラスを導入して、通信クラスをまとめる(iOS風)GET / POST / DELETE / PUT

More than 3 years have passed since last update.


【Android】Routerクラスを導入して、通信クラスをまとめる(iOS風)

kotlin_800x320.png


前提条件


  • AndroidStudio

  • Kotlin

  • OKHTTP3


はじめに


  • 私は主にiosアプリの方を中心に製作していますが、API通信にはAlamofireというライブラリをしばしば使います。

  • そのライブラリではswiftの旨味を生かした、Routerクラスというものがenumで定義されており、kotlinでもenumがあるので、それを生かして同じようにenumで定義しました。

  • KotlinとSwiftで同じようなコードが書ければ、アプリの保守性も上がると思い製作しました。


  • ですので、iOSのAlamofireを触ったことがある人には馴染めるかと思います。


  • 根っからの、アンドロイダーの方はもしかするとキモイかもしれんです。


  • 今はiOSでも、今後Androidを作るかもしれない人は役に立つかも!?



ようするに


  • Kotlinの旨味を生かして、通信クラスを実装しよう!!


機能定義


  • Kotlinで記述

  • https通信ができる

  • OKHttp3を使用する

  • apiのバージョニングができる

  • urlのpathを追加できる

  • get post delete put 等のhttpメソッドが叩ける

  • それぞれ、parameterをmapで渡すことができる

  • 通信は非同期でおこなう


サンプル


  • Router.kt (APIの種類を列挙します)

  • APIService.kt (実際の処理を記述)

  • TLSSocketFactory.kt (SSLで必要になります)


Router.kt


  • 叩くAPIに応じて、postやdeleteメソッドかを明示できたり、pathを列挙しています

  • addPathSegmentでチェーンすれば長いpathも作れます

  • 例) DeleteGroupだと (DELETE) https://xxx.xxxx.jp/api/v2/groups/delete (key=value,key2=value,...) というRequestが生成されます。


Router.kt

import okhttp3.*

enum class Router() {
GetVoice,
CreateUser,
UpdateUser,
CreateGroup,
GroupCheck,
DeleteGroup,
JoinGroup,
LeftGroup,
FinishJoin,
SelectUser,
GetStatus,
CallStandby,
Complete;

fun params(map: Map<String,String>?): Request {
return when (this) {
//httpメソッド
GetVoice -> get(map)
CreateUser -> post(map)
UpdateUser -> put(map)
CreateGroup -> post(map)
GroupCheck -> get(map)
DeleteGroup -> delete(map)
JoinGroup -> post(map)
LeftGroup -> put(map)
FinishJoin -> put(map)
SelectUser -> put(map)
GetStatus -> get(map)
CallStandby -> get(map)
Complete -> get(map)
}
}

// xx.xx.jp/api/v2/oooo <-pathを追加できます
private fun route(): HttpUrl.Builder {
return HttpUrl.Builder().scheme("https").host("xxx.xxxx.jp").addPathSegment("api").addPathSegment("v2").addPathSegment(
when (this) {
GetVoice -> "voice"
CreateUser, UpdateUser -> "users"
CreateGroup, GroupCheck, DeleteGroup, JoinGroup, LeftGroup, FinishJoin, SelectUser, GetStatus -> "groups"
CallStandby, Complete, TestPush -> "dials"
}
).addPathSegment(
// xx.xx.jp/api/v2/oooo/OOOO <-pathを追加できます
when (this) {
GroupCheck -> "check_start"
DeleteGroup -> "delete"
JoinGroup -> "join"
LeftGroup -> "left"
FinishJoin -> "start"
SelectUser -> "select"
GetStatus -> "status"
CallStandby -> "matching"
Complete -> "complete"
else -> ""
}
)
}
private fun createUrl(route: HttpUrl.Builder, query: Map<String,String>?): Request.Builder {
if (query == null) { return Request.Builder().url(route.build()) }
with(route) {
for ((key, value) in query) {
this.addQueryParameter(key,value)
}
}
return Request.Builder().url(route.build())
}

private fun createUrl(route: HttpUrl.Builder): Request.Builder {
return Request.Builder().url(route.build())
}

private fun post(map: Map<String,String>?) :Request {
return createUrl(route()).post(buildBody(map)).build()
}

private fun get(query: Map<String,String>?): Request {
return createUrl(route(),query).get().build()
}

private fun delete(map: Map<String,String>?) :Request {
return createUrl(route()).delete(buildBody(map)).build()
}

private fun put(map: Map<String,String>?): Request {
return createUrl(route()).put(buildBody(map)).build()
}

private fun buildBody(map: Map<String,String>?): RequestBody {
val body = with(MultipartBody.Builder()) {
map?.let {
for ((key, value) in map) {
addFormDataPart(key, value)
}
}
build()
}
return body
}
}



APIservice.kt


  • API通信の実際の処理はここにまとめます


APIService.kt

class APIService{

companion object {
//https通信で必要
val socketFactory: SSLSocketFactory?
get() = TLSSocketFactory()

fun createUser(token: String, name: String, sex: Sex, handler: (String) -> Unit) {
val map = mapOf("device_token" to token,
"name" to name,
"sex" to sex.rawval().toString(),
"device" to "android")

this.enqueueAccess(Router.CreateUser.params(map), { result ->
val user_id = result.getString("user_id")
handler(user_id)
}, { code, message ->
//error処理
})
}

fun createGroup(name: String, handler: (String) -> Unit) {
val map = mapOf("host_user_id" to user.userId.toString(),
"name" to name)
this.enqueueAccess(Router.CreateGroup.params(map), { res ->
val groupId = res.getString("group_id")
handler(groupId)
}, { code, message ->
//error処理
})
}

fun deleteGroup(success: () -> Unit) {
val map = mapOf("host_id" to user.userId,
"group_id" to group.groupId)
this.enqueueAccess(Router.DeleteGroup.params(map), { res ->
success()
}, { code, message ->
  //error処理

})
}

private fun enqueueAccess(request: Request, successHandler: (JSONObject) -> (Unit), errorHandler: (Int, String?) -> (Unit)) {
object : AsyncTask<Void, Void, String>() {
protected override fun doInBackground(vararg params: Void): String? {
var result: String? = null
val client = object : OkHttpClient() {
override fun sslSocketFactory(): SSLSocketFactory? {
return socketFactory
}
}
try {
val response = client.newCall(request).execute()
val code = response.code()
if (code < 200 || code >= 300) {
return null
}
result = response.body().string()
} catch (e: IOException) {
e.printStackTrace()
}
return result
}

protected override fun onPostExecute(result: String?) {
if (result == null) { errorHandler(999,"error!!"); return }
val json = JSONObject(result)
val code = json.getInt("code")
if (code >= 200 && code < 300) {
successHandler(json)
} else {
errorHandler(code,"error message")
}
}
}.execute()
}
}
}



TLSSocketFactory.kt


  • https通信で必要(httpでいい人はいらない)


TLSSocketFactory.kt

class TLSSocketFactory @Throws(KeyManagementException::class, NoSuchAlgorithmException::class)

constructor() : SSLSocketFactory() {

private val internalSSLSocketFactory: SSLSocketFactory

init {
val context = SSLContext.getInstance("TLS")
context.init(null, null, null)
internalSSLSocketFactory = context.socketFactory
}

override fun getDefaultCipherSuites(): Array<String> {
return internalSSLSocketFactory.defaultCipherSuites
}

override fun getSupportedCipherSuites(): Array<String> {
return internalSSLSocketFactory.supportedCipherSuites
}

@Throws(IOException::class)
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose))
}

@Throws(IOException::class, UnknownHostException::class)
override fun createSocket(host: String, port: Int): Socket {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port))
}

@Throws(IOException::class, UnknownHostException::class)
override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort))
}

@Throws(IOException::class)
override fun createSocket(host: InetAddress, port: Int): Socket {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port))
}

@Throws(IOException::class)
override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort))
}

private fun enableTLSOnSocket(socket: Socket?): Socket {
if (socket != null && socket is SSLSocket) {
socket.enabledProtocols = arrayOf("TLSv1.1", "TLSv1.2")
}
return socket!!
}
}



使い方


UserModel.kt


  • ModelクラスなどからAPIを呼び出すだけでよくなります。


UserModel.kt

class UserModel {

companion object {
fun createUer(name:String,sex: Sex) {
val token = UserDefault.getPreferenceValue(PreferenceKey.KEY_DEVICE_TOKEN)
//APIをよびだすYO!
APIService.createUser(token.toString(), name, sex) { userId ->
val user = User(userId, name, sex)
UserViewModel.user.value = user
}
}
}
}


まとめ


  • KotlinとSwiftで同じようなコードが書ければ、アプリの保守性も上がるかと思います。

  • Androidに関しては初心者なので、指摘等あればおしらせください。


参考

https://github.com/Alamofire/Alamofire