如何解决 kotlin 项目中的“无法访问代码”错误?

How can I solve "Unreachable code" error in my kotlin project?

提问人:NewPartizal 提问时间:6/8/2023 最后编辑:NewPartizal 更新时间:6/9/2023 访问量:150

问:

SplashActivity.kt (英语)

 @AndroidEntryPoint
    @SuppressLint("CustomSplashScreen")
    class SplashActivity : ComponentActivity() {
    
        private val viewModel: SplashScreenViewModel by viewModels()
        private val tokenManager = TokenManager(this)
        val activity = this
        private val errMsg = "Bir hata ile karşılaşıldı."
    
        private fun navigateToMain(isTokenExist: Boolean) {
            val intent = Intent(this@SplashActivity, MainActivity::class.java)
            intent.putExtra("isTokenExist", isTokenExist)
            startActivity(intent)
            finish()
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                val splashScreen = installSplashScreen()
                splashScreen.setKeepOnScreenCondition { true }
            }
            super.onCreate(savedInstanceState)
    
    
            if (viewModel.state.value.error) {
                Toast.makeText(this@SplashActivity, errMsg, Toast.LENGTH_LONG).show()
            }
            lifecycleScope.launch(Dispatchers.IO) {
                tokenManager.getRefreshToken().collect { refreshToken ->
                    if (refreshToken != null) {
                        if (viewModel.state.value.error) {
                            delay(2000)
                            activity.navigateToMain(false)
                        }
                        viewModel.state.collect {
                            activity.navigateToMain(!it.error)
                        }
                        activity.viewModel.refreshAccessToken(refreshToken) //unreachable code warning
                    } else {
                        delay(2000)
                        activity.navigateToMain(false)
                    }
                }
            }
        }
    }

SplashScreenViewModel.kt

 @HiltViewModel
    class SplashScreenViewModel @Inject constructor(
        private val tokenManager: TokenManager
    ) : ViewModel() {
    
        private val _state = MutableStateFlow(SplashScreenState())
        val state: StateFlow<SplashScreenState> = _state.asStateFlow()
    
    
        fun refreshAccessToken(refreshToken: String) {
    
            viewModelScope.launch {
                try {
                    val loggingInterceptor = HttpLoggingInterceptor()
                    loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
                    val okHttpClient = OkHttpClient
                        .Builder()
                        .addInterceptor(loggingInterceptor)
                        //.authenticator(AuthAuthenticator(tokenManager))
                        .build()
    
                    val retrofit = Retrofit.Builder()
                        .baseUrl(Constants.BASE_URL)
                        .addConverterFactory(GsonConverterFactory.create())
                        .client(okHttpClient)
                        .build()
    
                    val service = retrofit.create(RegisterService::class.java)
    
                    val response = service.refreshToken(model = RefreshToken(refreshToken))
    
                    if (response.isSuccessful && response.body() != null) {
                        val newAccessToken = response.body()?.access_token
                        val newRefreshToken = response.body()?.refresh_token
                        if (newAccessToken != null && newRefreshToken != null) {
                            tokenManager.saveAccessToken(newAccessToken)
                            tokenManager.saveRefreshToken(newRefreshToken)
                        }
                    }
                    _state.update {
                        it.copy(
                            completed = true
                        )
                    }
                } catch (e: Exception) {
                    _state.update {
                        it.copy(
                            error = true,
                            completed = true
                        )
                    }
                }
            }
        }
    }
    
    data class SplashScreenState(
        val error: Boolean = false,
        var completed:Boolean = false
    )

令牌管理器.kt

class TokenManager(private val context: Context) {

    companion object {
        private val ACCESS_TOKEN  = stringPreferencesKey("access_token")
        private val REFRESH_TOKEN = stringPreferencesKey("refresh_token")
        private val EXPIRATION_TIME = longPreferencesKey("expiration_time")
    }

    fun getAccessToken(): Flow<String?> {
        return context.dataStore.data.map { preferences ->
            preferences[ACCESS_TOKEN]
        }
    }

    suspend fun saveAccessToken(token: String) {
        context.dataStore.edit { preferences ->
            preferences[ACCESS_TOKEN] = token
            val expirationTime = System.currentTimeMillis()
            preferences[EXPIRATION_TIME] = expirationTime + 86400 * 1000
        }
    }

    fun getAccessTokenExpirationTime(): Flow<Long?> {
        return context.dataStore.data.map { preferences ->
            preferences[EXPIRATION_TIME]
        }
    }

    suspend fun deleteAccessToken() {
        context.dataStore.edit { preferences ->
            preferences.remove(ACCESS_TOKEN)
        }
    }

    fun getRefreshToken(): Flow<String?> {
        return context.dataStore.data.map { preferences ->
            preferences[REFRESH_TOKEN]
        }
    }

    suspend fun saveRefreshToken(token: String) {
        context.dataStore.edit { preferences ->
            preferences[REFRESH_TOKEN] = token
            val expirationTime = System.currentTimeMillis()
            preferences[EXPIRATION_TIME] = expirationTime + 86400 * 1000
        }
    }

    suspend fun deleteRefreshToken() {
        context.dataStore.edit { preferences ->
            preferences.remove(REFRESH_TOKEN)
        }
    }

我收到“无法访问”代码警告。我在上面分享了我的启动活动和 viewodel 代码。我认为我制作的代码是正确的,但是有一部分我不喜欢,为什么android studio会发出这个警告。

  viewModel.state.collect {
                        activity.navigateToMain(!it.error)
                    }
                    activity.viewModel.refreshAccessToken(refreshToken) // --> Unreachable code warning

在这行代码中,activity.navigateToMain(!it.error)

无论如何,当状态发生变化时不起作用?

所以第一次没有变化,那么它就不起作用了,下面的代码就可以工作了 所以 activity.viewModel.refreshAccessToken(refreshToken) 对吧?

如果是这种情况,逻辑是正确的,但如果 navigateToMain 有效,那么第一次发出 Unreachable 代码警告是正常的,因为在导航后,它会转到 mainActivity 并终止 splashActivity,然后 activity.viewModel.refreshAccessToken(refreshToken) 行不起作用。我所做的是对的吗?还是有错误?我不确定,所以我想问你能帮忙吗?

android kotlin 异步 android-lifecycle

评论


答:

1赞 Tenfour04 6/8/2023 #1

StateFlow 永远不会完成,因此当您调用 时,调用下面的任何代码都不会被访问,因为永远不会返回。collectstatecollectcollect


编辑:

按照 StateFlow 的设置方式,当您准备好导航时,它将具有一个值。因此,代码的快速简便的解决方法是将complete == true

viewModel.state.collect {
    activity.navigateToMain(!it.error)
}

val refreshResult = viewModel.state.first { it.completed }
activity.navigateToMain(!refreshResult.error)

该函数将挂起,直到发出一个值,该值表示 lambda 中的条件,然后返回该值。first

此外,将此块放在您离开屏幕的位置,但随后允许其余逻辑继续。你要么需要在这个块内部调用,要么将它下面的代码包装在一个块中。if (viewModel.state.value.error) {return @launchifelse

您还需要在通话后删除。协程中没有需要它的阻塞代码,您需要在活动之间导航。(Dispatchers.IO)launchMain

可选:这就是我设计 ViewModel 类的方式,以避免在重新创建 Fragment 并再次调用时冗余地重新启动提取的可能性。refreshAccessToken()

class SplashScreenViewModel @Inject constructor(
    private val tokenManager: TokenManager
) : ViewModel() {

    private val _state = MutableStateFlow(SplashScreenState())
    val state: StateFlow<SplashScreenState> = _state.asStateFlow()

    private val refreshJob: Job? = null

    fun refreshAccessToken(refreshToken: String) {
        if (refreshJob != null) {
            return
        }
        refreshJob = viewModelScope.launch {
            // your original code  

            // After all code from your original coroutine, if you want to 
            // support ability to call this function again to do another refresh:
            refreshJob = null
        }
    }

    //...
}

评论

0赞 NewPartizal 6/8/2023
你能分享一下示例代码吗?
0赞 Tenfour04 6/8/2023
我必须看到你的 TokenManager 类。但是,重新设计整个过程并非易事,尤其是在不知道您的应用程序需要执行的所有操作以及它应该如何运行的情况下。
0赞 NewPartizal 6/8/2023
我分享了我的代币管理器,你可以看到
0赞 Tenfour04 6/9/2023
实际上,当我仔细观察它时,我认为使用 Flow 来保存令牌刷新的结果是可以的。您确实需要保留正在进行的工作,以防在等待结果时重新创建片段,因此需要热流(您可以使用延迟,但我们可能希望能够再次刷新)。我更新了一些建议。