LoginSignup
12
12

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-09-08

【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に関しては初心者なので、指摘等あればおしらせください。

参考

12
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
12