android - 处理 MVI Kotlin 应用程序中的安全令牌失败
问题描述
我有一个使用单个活动在 Kotlin 中构建的自定义应用程序,遵循 MVI。
ViewModel -> Repository -> Rertofit API 的基本模式
用户登录并获得一个令牌,然后此令牌用于所有后续 API 调用。最终此令牌过期或可以在后端过期。
我试图弄清楚如何在低级别以干净的方式处理过期的令牌,而不是用处理过期令牌的逻辑来污染我的所有片段。
如果令牌过期,我希望将用户带到登录页面/片段。
解决方案
以下是我认为可以做到的方式。首先,定义一个提供登出契约的接口。例如:
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()
防止同时从多个活动/片段调用注销操作。
推荐阅读
- marklogic - XDMP-ELEMRIDXNOTFOUND: cts:element-values -- MarkLogic 10 [marklogic] 中没有元素范围索引
- reactjs - 组件没有更新,我真的不明白为什么
- javascript - 如何将项目或目录的所有文件中的所有cjs文件重构为es6模块?(vscode)
- multithreading - 发件箱图案弹簧卡夫卡
- python - 对于多处理星图来说,这种加速正常吗?
- performance - TTFB(第一个字节的时间)分数是否仅取决于服务器/虚拟主机?
- encryption - Envoy 的问题:“无法读取 server-key.pem”
- javascript - this.props 出于某种原因为空(React.js)
- mysql - 访问被拒绝将 node.js 连接到 ClearDB
- swiftui - 在 SwiftUI 中使用 onAppear 时的无限循环