Realm + Retrofit + OkHttp を利用して API をコールするアプリを作ろうと設計しています。ログインが成功すると accessToken と refreshToken をサーバーから返却する仕組みにしていて、返却された値を Realm に保存し、次回以降は Realm から accessToken を取得することを考えています。

accessToken には有効期限があり、有効期限が切れた accessToken を用いて API をコールすると 401 エラーが返却され、refreshToken を利用して accessToken を更新する仕様で進めているのですが、 Realm に保存した情報が上手く取得できず、どこに問題があるか教えていただけないでしょうか?

(現象)

  1. accessToken の有効期限が切れた状態で、Retrofit を用いて 2 つの非同期なリクエストを同時に投げる(リクエストA, B)
  2. accessToken の有効期限が切れている為 A, B とも 401エラーが返却される
  3. TokenAuthenticator の authenticate メソッドが A , B とも呼ばれる
  4. A の updateToken は成功(タイミングによっては B が成功)。Realm の accessToken と refreshToken が更新される。 B の updateToken は A のリクエストが成功した為、失敗する(すでにサーバーが保持している token 情報が更新されている為)
  5. OkHttp の仕様で B の authenticate 呼ばれ続けるが、Realm 内の token 情報が更新されていても ソース内の ※1 では古い token 情報(【4】で更新される前の情報)が常に返却されてしまう

環境は Kotlin 1.0.4, Realm 2.2.0, OkHttp 3.3.1。ソースの抜粋は以下になります。

・Token.kt

open class Token(
    // token 情報は 1件のみ保存すれば良いので id = 1 を固定で指定
    @PrimaryKey open var id: Int = 1,
    open var accecctoken: String = "",
    open var refreshToken: String = "",
) : RealmObject()

・TokenAuthenticator.kt

open class TokenAuthenticator : Authenticator {
    // 401 エラーの際に呼び出される
    override fun authenticate(route: Route, response: Response): Request? {
        // 保存してある Token 情報を取得
        val realm = Realm.getDefaultInstance()
        val token = realm?.where(Token::class.java)?.findFirst() ?: return null (※1)
        realm.close()

        // refreshToken を利用して token 更新のリクエストを同期処理で行う
        this.updateToken(token.refreshToken)

        // updateToken メソッドで token が更新されるので、新しい token をセットする
        val newAccessToken = "Bearer ${token.accessToken}"
        return response.request().newBuilder().header("www-Authorization", newAccessToken)?.build()
    }

    private fun updateToken(refreshToken: String) {
        val call = // retrofit で token を取得する call を設定
        // retrofit で新しい Token を取得する。取得に失敗したら return する
        val newToken: Token? = call.execute().body() ?: return

        // 取得した結果を Realm に保存する
        Realm.getDefaultInstance().use { realm ->
            realm.executeTransaction {
                realm.copyToRealmOrUpdate(newToken)
            }
        }
    }
}