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

huangapple go评论69阅读模式
英文:

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

问题

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 = "An error occurred."

    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
)

TokenManager.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)
        }
    }
}

You have mentioned that you are receiving an "Unreachable code" warning for the line activity.viewModel.refreshAccessToken(refreshToken). This warning is occurring because the code analyzer is detecting that this line of code is never executed due to the navigateToMain function being called before it.

The logic you have described seems correct. If navigateToMain is called, it will start the MainActivity, and the finish() method will be executed, effectively ending the SplashActivity. As a result, the code after navigateToMain will not be executed.

The warning is there to inform you that the code might be unreachable under certain conditions, which is indeed the case in this scenario. If you are sure that this is the intended behavior and that activity.navigateToMain(false) should terminate the SplashActivity, you can ignore the warning.

In summary, the warning is there to help you identify potential issues, but in this case, it reflects the intended flow of your code.

英文:

SplashActivity.kt

 @AndroidEntryPoint
    @SuppressLint(&quot;CustomSplashScreen&quot;)
    class SplashActivity : ComponentActivity() {
    
        private val viewModel: SplashScreenViewModel by viewModels()
        private val tokenManager = TokenManager(this)
        val activity = this
        private val errMsg = &quot;Bir hata ile karşılaşıldı.&quot;
    
        private fun navigateToMain(isTokenExist: Boolean) {
            val intent = Intent(this@SplashActivity, MainActivity::class.java)
            intent.putExtra(&quot;isTokenExist&quot;, isTokenExist)
            startActivity(intent)
            finish()
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
    
            if (Build.VERSION.SDK_INT &gt;= 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 -&gt;
                    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&lt;SplashScreenState&gt; = _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 &amp;&amp; response.body() != null) {
                        val newAccessToken = response.body()?.access_token
                        val newRefreshToken = response.body()?.refresh_token
                        if (newAccessToken != null &amp;&amp; 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
    )

TokenManager.kt

class TokenManager(private val context: Context) {

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

    fun getAccessToken(): Flow&lt;String?&gt; {
        return context.dataStore.data.map { preferences -&gt;
            preferences[ACCESS_TOKEN]
        }
    }

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

    fun getAccessTokenExpirationTime(): Flow&lt;Long?&gt; {
        return context.dataStore.data.map { preferences -&gt;
            preferences[EXPIRATION_TIME]
        }
    }

    suspend fun deleteAccessToken() {
        context.dataStore.edit { preferences -&gt;
            preferences.remove(ACCESS_TOKEN)
        }
    }

    fun getRefreshToken(): Flow&lt;String?&gt; {
        return context.dataStore.data.map { preferences -&gt;
            preferences[REFRESH_TOKEN]
        }
    }

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

    suspend fun deleteRefreshToken() {
        context.dataStore.edit { preferences -&gt;
            preferences.remove(REFRESH_TOKEN)
        }
    }

I'm getting an Unreachable code warning. I shared my splash activity and viewodel codes above. I think that the codes I made are correct, but there is a part that I do not like, why android studio gives this warning.

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

In this line of code will activity.navigateToMain(!it.error)

not work when there is a change in state anyway?

so there is no change in the first time, then it will not work and the following code will work so activity.viewModel.refreshAccessToken(refreshToken) right ?

If this is the case, the logic is correct, but if navigateToMain works, then it is normal to give an Unreachable code warning at the first time because after navigating, it goes to the mainActivity and this terminates the splashActivity, then the activity.viewModel.refreshAccessToken(refreshToken) line does not work. Is it right what I did? or is there an error? I'm not sure so I wanted to ask can you help?

答案1

得分: 1

A StateFlow永远不会完成,所以当您在state上调用collect时,collect调用下面的任何代码都不会被执行,因为collect永远不会返回。

编辑:

按照您设置的StateFlow方式,当您准备导航时,它将具有complete == true值。所以您代码的快速简单修复是用以下代码替换:

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

first函数会暂停,直到满足lambda中的条件并发出一个值,然后返回该值。

另外,在您导航离开屏幕的地方添加这个 if (viewModel.state.value.error) { 块,但允许继续执行其余的逻辑。您可以在此if块内调用return @launch,或者在其下面的代码中包装一个else块。

您还需要删除launch调用后的(Dispatchers.IO)。在您的协程中没有阻塞代码需要它,并且在活动之间导航时需要在Main上运行。

可选:这是我设计ViewModel类的方式,以避免在Fragment重新创建并再次调用refreshAccessToken()时可能重复重新启动获取操作。

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

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

    private var refreshJob: Job? = null

    fun refreshAccessToken(refreshToken: String) {
        if (refreshJob != null) {
            return
        }
        refreshJob = viewModelScope.launch {
            // 您的原始代码  

            // 在您的原始协程中的所有代码之后,如果要支持再次调用此函数进行另一次刷新:
            refreshJob = null
        }
    }

    //...
}
英文:

A StateFlow never completes, so when you call collect on state, none of the code below the collect call will ever be reached, because collect will never return.


Edit:

The way you have your StateFlow set up, it will have a complete == true value when you are ready to navigate. So the quick and easy fix for your code is to replace

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

with

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

The first function suspends until a value is emitted that means the criteria in the lambda, and then it returns that value.

Also, have this if (viewModel.state.value.error) { block where you navigate away from the screen, but then allow the rest of your logic to continue. You either need to call return @launch inside this if block or wrap the code below it in an else block.

You also need to remove (Dispatchers.IO) after your launch call. There is no blocking code in your coroutine that would require it, and you need to be on Main to navigate between activities.

Optional: This is how I would design the ViewModel class to avoid the possibility of redundantly restarting the fetch if the Fragment is recreated and calls refreshAccessToken() again.

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

    private val _state = MutableStateFlow(SplashScreenState())
    val state: StateFlow&lt;SplashScreenState&gt; = _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
        }
    }

    //...
}

huangapple
  • 本文由 发表于 2023年6月8日 22:23:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/76432845.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定