首页 > 解决方案 > 处理 MVI Kotlin 应用程序中的安全令牌失败

问题描述

我有一个使用单个活动在 Kotlin 中构建的自定义应用程序,遵循 MVI。

ViewModel -> Repository -> Rertofit API 的基本模式

用户登录并获得一个令牌,然后此令牌用于所有后续 API 调用。最终此令牌过期或可以在后端过期。

我试图弄清楚如何在低级别以干净的方式处理过期的令牌,而不是用处理过期令牌的逻辑来污染我的所有片段。

如果令牌过期,我希望将用户带到登录页面/片段。

标签: androidkotlin

解决方案


以下是我认为可以做到的方式。首先,定义一个提供登出契约的接口。例如:

interface LogOutOwner {
    fun logout(): Observable<Unit>
}

实现这个接口,这里我使用拦截器作为实现,因为它提供了足够的信息来推断是否需要注销:

class ErrorInterceptor : Interceptor, LogOutOwner {

    private val publishLogOutSubject: PublishSubject<Unit> = PublishSubject.create()

    override fun intercept(chain: Interceptor.Chain): Response {
        val response = chain.proceed(chain.request())
        if (response.code == 401) { // check if a logout is needed
            publishLogOutSubject.onNext(Unit)
        }
        return response
    }

    override fun logout(): Observable<Unit> {
        return publishLogOutSubject
    }
}

为了让你的拦截器工作,LogOutOwner应该是单例,所以他们处理你所有的网络请求。实现此目的的方法之一是使用依赖注入框架。这里作为一个例子,我展示了手动依赖注入:

object Injection {
    private val errorInterceptor = ErrorInterceptor()

    fun provideLogOutOwner(): LogOutOwner = errorInterceptor

    private fun provideOkHttpClient(): OkHttpClient {
        val httpClient = OkHttpClient.Builder()
        httpClient.addInterceptor(errorInterceptor)
        return httpClient.build()
    }

    fun provideRetrofit() = Retrofit.Builder()
            .baseUrl("")
            .addConverterFactory(GsonConverterFactory.create())
            .client(provideOkHttpClient())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()
}

由于Kotlin对象只有一个实例,errorInterceptor因此在 的范围内也只实例化一次Injection。最后,现在您需要订阅以LogOutOwner侦听注销事件。由于您只有一个活动,您可以在那里订阅并打开您需要的任何片段。但是通过这样做,您最终需要处理取消注销订阅,并且每当您想要处理不同活动或片段的注销时,您都需要实现相同的取消逻辑。为了使其更通用,无需在需要处理注销时引入相同的样板,请考虑使用生命周期感知组件。这是一个例子:

    class LogOutObserver(
    private val logOutOwner: LogOutOwner,
    private val logoutAction: () -> Unit
) :
    LifecycleObserver {

    private var disposable: Disposable? = null

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onStart() {
        disposable = logOutOwner.logout()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe { logoutAction.invoke() }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onStop() {
        disposable?.dispose()
    }
}

当您需要处理注销时,只需使用此观察器,如下所示:

lifecycle.addObserver(LogOutObserver(Injection.provideLogOutOwner()) {
    // do logout
})

因此,通过引入上面的所有代码,我们减少了活动或片段应该实现的逻辑量。基本上,活动或片段现在只需要关注导航逻辑,而无需担心导致注销或任何生命周期事件的原因。我在这里使用了 RxJava,但我想如果需要的话,用 Kotlin Coroutines 实现它并不难。

PS如果您需要它与多个片段/活动一起使用,您需要进行更改LogOutObserver,以便它订阅/取消订阅,onResume()onPause()防止同时从多个活动/片段调用注销操作。


推荐阅读