代码之家  ›  专栏  ›  技术社区  ›  nero-123

在Repository类中同时存储访问令牌。Kotlin

  •  0
  • nero-123  · 技术社区  · 2 年前

    我正在构建一个使用Android Studio和Kotlin的登录活动。我设法进行了所需的API调用,但现在我想将API响应中的访问令牌本地存储在Room DB中。我的问题是,我希望在收到调用的响应后立即将其发送回LoginViewModel,同时将令牌保存在DB中。

    //in LoginModelView
    fun onLoginButtonPress(email:String, password:String ) {
            loginState.value= LoginState(loading = true)
            val requestBody: LoginRequest = LoginRequest(email,password)
    
            viewModelScope.launch{
                val isAuthorized : Boolean = _repository.makeLoginRequest(requestBody)
                if (isAuthorized){
                    loginState.value= LoginState(authorized = true)
                }else{
                    loginState.value= LoginState(credentialsDeclined = true)
                }
            }
    
        }
    
    // LoginRepository
    class LoginRepository {
        suspend fun makeLoginRequest(requestBody: LoginRequest): Boolean{
            var isAuth: Boolean = false
            try {
                val response = apiService.login(requestBody)
                when (response.code()){
                    200 -> isAuth = true
                    404 -> isAuth=false
                }
    
                response.body()?.data?.let { Log.d("token", it.Token) }
    
            }catch (e:Exception){
                e.message?.let { Log.d("LoginViewModelError", it) }
            }
            if (isAuth){
                viewModelScope
                // launch coroutine to save some data into Room DB
    
    
                }
            }
            return isAuth
        }
    }
    
    

    我可以弄清楚Room DB方面,但我不确定并发方面。建议的解决方法是什么?我不能使用viewModelScope.slaunch,在例程内启动协同程序似乎是个坏主意,因为父协同程序可能会在子协同程序完成之前终止。我想也许我可以让令牌成为从另一个存储库类观察到的LiveData,但这似乎不够有效,因为令牌需要写一次

    1 回复  |  直到 2 年前
        1
  •  0
  •   tyg    2 年前

    你想要 makeLoginRequest 以执行两个IO操作,这两个操作应移出主线程并异步运行:

    1. 互联网上的HTTP请求
    2. 本地文件系统上的数据库访问

    您可以在视图模型范围内的新协同程序中启动它们。缺少的是 makeLoginRequest 应切换到IO调度器

    suspend fun makeLoginRequest(requestBody: LoginRequest): Boolean = withContext(Dispatchers.IO) {
        ...
    }
    

    只需更换 return isAuth 具有 isAuth 你可以走了。这样可以处理操作#1。

    现在来谈谈手头的问题,2号操作应该在哪里进行。您的想法是启动另一个协同程序,这样您就可以立即返回操作#1的结果。虽然这是可能的,但我想质疑你为什么要这么做。是的,在让视图模型继续使用已经可用的结果之前,等待操作#2完成似乎是在浪费时间。但话说回来,视图模型已经需要等待相当长的时间(才能完成通过互联网的网络访问)。然而,再加上本地文件系统上数据库访问造成的延迟,不会产生任何可测量的差异,因为它将比互联网访问快得多。我的建议是在HTTP请求完成后将令牌同步存储在数据库中 然后 返回到视图模型。这是一个简单的解决方案。

    然而,如果您真的、真的想返回到视图模型,并且数据库访问在后台异步运行,那么您需要启动一个新的协同程序。这本身并不是坏事,事实上 structured concurrency (协同工作背后的原则)就是关于。你不应该害怕协同出游被取消:取消协同出游是合作的。您的数据库访问不会因为其运行的协同程序作用域被取消而中断。不过,如果你在协同程序中进行了长时间的操作,你应该积极检查协同程序是否被取消,以满足该请求并终止你的操作。在你的情况下,你不想那样,所以不要这样做。:)

    另一件事是,无论如何,视图模型的范围都不会那么容易被取消。例如,它将在触发重新创建活动的配置更改中幸存下来-另一方面,视图模型及其范围不会受到它的影响 view model's lifecycle 了解更多详细信息。

    现在,要启动一个新的协同程序,首先需要一个协同程序范围。这个 viewModelScope 但是,在存储库中不可用。您总是可以像这样在suspend函数中创建一个新的作用域,不过:

    coroutineScope { 
        launch { 
            // database access
        }
    }
    

    但这对你没有帮助。虽然 launch 将立即返回, coroutineScope 将阻塞,直到所有已启动的协同程序完成。毕竟,您必须等待数据库访问完成。

    相反,您需要另一个范围。该范围应作为参数传递给您的函数:

    suspend fun makeLoginRequest(
        requestBody: LoginRequest,
        scope: CoroutineScope,
    ): Boolean = withContext(Dispatchers.IO) {
        ...
    
        scope.launch {
            // database access
        }
    
        isAuth
    }
    

    视图模型中的函数调用现在需要提供作用域。这个 视图模型范围 是很好的候选人。由于您的函数调用已经位于该范围内,因此可以使用以下方法:

    val isAuthorized: Boolean = _repository.makeLoginRequest(requestBody, this)
    

    说到这里,我想重申:只需使用简单的解决方案,并完全跳过协同程序。不过,请记住切换到IO调度器。网络访问和数据库访问应该始终在该调度器上完成。