英文:
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("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
)
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)
}
}
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) // --> 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<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
}
}
//...
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论