Kotlin & AndroidでIIJmioにログインする

#目的
Kotlinで開発するAndroidアプリに，IIJmioのログイン処理（トークン取得）機能を実装します．
IIJmioに限らず，OAuthを利用するアプリ全般に流用可能かと思います．
Kotlinに触り始めたのはごく最近なので，Javaそのままなのはあしからず．

#準備
IIJmioのAPIを利用するには，IIJmioのデベロッパIDが必要になります．　
[IIJmioの公式ページ](https://www.iijmio.jp/hdd/coupon/mioponapi.jsp)を参考に利用登録を行ってください．

#ログイン処理の方法
この記事ではWebViewを利用せずに，別のブラウザアプリ（Chromeなど）を利用して，以下の流れでトークンを取得する方法を紹介します．
1. アプリでログイン用ページのURIを格納したインテントを投げてブラウザを起動する
2. ブラウザ側でログイン処理を行い，アプリにリダイレクトする
3. アプリに戻ってきて，ログイン結果（トークンなど）を取得する

#コード
## URI用意，ブラウザ起動部分
URIの構成は前述のIIJmioの公式ページでも説明されています．
`R.string.developer_id`は`strings.xml`に`<string name="developer_id">各自取得したデベロッパID</string>`と定義してあります．
コールバック時に使用するURIの`:`や`/`をエスケープしなければならないことに注意してください．
また，IIJmio側にリダイレクト用URIを登録しなければならないのですが，自分はこの登録の時に無駄な半角スペースを付けてしまい，無駄な時間を過ごしてしまったので注意してください．

``` Kotlin:MainActivity.kt（抜粋）
private fun startOAuth() {

    val uri = "https://api.iijmio.jp/mobile/d/v1/authorization/?" +
        "response_type=token" +
        "&client_id=" + getString(R.string.developer_id) +
        "&redirect_uri=" + getString(R.string.simple_app_name) + "%3A%2F%2Fcallback" +
        "&state=" + "success"

    Log.d("request URI : ", uri)

    val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri))
    startActivity(intent)
}
```

## ブラウザからのトークン受け取り部分
ブラウザアプリにいったんフォーカスされてから目的のアプリに戻ってくるので，トークン受け取り処理は`onResume()`に書きます．

``` Kotlin:MainActivity.kt（抜粋）
public override fun onResume() {
    super.onResume()

    val intent = intent
    val action = intent.action

    if (action == Intent.ACTION_VIEW) {
        val uri: Uri? = intent.data
        if (uri != null) {

            // 受け取るURIが
            // simplemio://callback#access_token=token&state=success&token_type=Bearer&expires_in=7776000
            // となっていて，正しくエンコードできないので # を ? に置き換える

            var uriStr = uri.toString()
            uriStr = uriStr.replace('#', '?')
            val validUri = Uri.parse(uriStr)

            val token = validUri.getQueryParameter("access_token")
            val state = validUri.getQueryParameter("state")

            if (state != "success") {
                Toast.makeText(this, "正しく認証することができませんでした．", Toast.LENGTH_LONG).show()
            } else {
                MioUtil.saveToken(this, token)
                Toast.makeText(this, "トークン:" + token, Toast.LENGTH_LONG).show()
            }
        }
    }
}
```
コメントにも書いてありますが，mioから渡されるURIの中で，トークンを含むすべてのクエリが`#`のあと（フラグメントというらしい）に書かれているため，受け取ったそのままのURIで`URI#getQueryParameter(key:String)`を呼び出すと`null`が返ってきて例外が発生してしまいます．なので，ここでは`#`を`?`に置き換えてからトークンの文字列を取り出しています．

さらに，ブラウザが投げたインテントを拾えるように目的のアプリにインテントフィルタを設定します．
コールバック用URIには`hogehoge://callback`を指定します．これは`AndroidManifest.xml`に登録するものも`startOAuth()`でURIに含まれるものも，IIJmioに登録するものも同じです．以下の`android:scheme="@string/simple_app_name"`がhogehogeに該当します．
また，ブラウザから目的のアプリに戻ってくる（呼び出される）ときに，新しいActivityが呼び出されるのではなく，同じActivityが呼び出されないといけないので，`android:launchMode="singleTask"`を追加します．

``` XML:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.otsuka.simplemio">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"

            <!-- ここも忘れずに -->
            android:launchMode="singleTask"

            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <!-- 追加ここから -->
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data
                    android:host="callback"
                    android:scheme="@string/simple_app_name" />
            </intent-filter>
            <!-- 追加ここまで -->

        </activity>
    </application>

</manifest>
```

#おわりに
閲覧ありがとうございました．
みなさんのアプリ開発の助けになれば幸いです．