You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

LaunchedEffect工作原理及用户存在判断逻辑异常排查

LaunchedEffect执行异常排查与核心机制解析

问题背景

启动页中通过account.get()检查用户会话,根据结果设置userExist,并将result设为true以触发LaunchedEffect执行导航逻辑。但实际运行时,当userExist为true时,LaunchedEffect却执行了else分支。

代码复现

SplashUiState

data class SplashUiState(
  val result: Boolean = false,
  val userExist: Boolean = false,
  val error: List<Pair<String, String>> = emptyList()
)

SplashViewModel

private val _state = MutableStateFlow(SplashUiState())
val state = _state.asStateFlow()

init {
  // 原代码直接调用suspend函数会编译错误,实际应使用viewModelScope启动协程
  viewModelScope.launch {
    delay(2000)
    checkSessionExist()
  }
}

private suspend fun checkSessionExist() {
    when(val account = repository.getAccount()) {
        is ResponseResult.Error -> {
            Log.d("SplashViewModel", "${account.code} ${account.message}")
            _state.update { 
                it.copy(
                    result = true, 
                    userExist = false,
                    error = listOf(
                        "code" to "${account.code}",
                        "message" to "${account.message}"
                    )
                ) 
            }
        }

        is ResponseResult.Success -> {
            Log.d("SplashViewModel", "${account.data}")
            _state.update { 
                it.copy(result = true, userExist = true) 
            }
        }
    }
}

SplashScreen

val state by viewModel.state.collectAsState()

LaunchedEffect(key1 = state.result) {
  if (state.userExist) {
    Log.d("LaunchedEffect", "user exist")
    navController.navigate(AppRoutes.OVIDE_SCREEN) {
      popUpTo(AppRoutes.SPLASH_SCREEN) {
        inclusive = true
      }
    }
  } else {
    Log.d("LaunchedEffect", "user not exist")
    navController.navigate(AppRoutes.WELCOME_SCREEN) {
      popUpTo(AppRoutes.SPLASH_SCREEN) {
        inclusive = true
      }
    }
  }
}

问题原因

  1. LaunchedEffect首次执行时机过早:
    首次组合时,state.result为初始值false,LaunchedEffect会立即启动协程,此时userExist也是false,直接执行else分支导航到欢迎页,并且通过popUpTo将启动页从栈中完全移除。
  2. 后续状态更新无法触发新逻辑:
    2秒后checkSessionExist执行完毕,更新state.result为true、userExist为true,但此时启动页已经被销毁,不会发生重组,新的LaunchedEffect协程根本没有机会启动。你看到的user not exist日志是首次执行的结果,并非userExist为true时的执行逻辑。
  3. 对LaunchedEffect工作机制的误解:
    • LaunchedEffect在Composable首次组合时,无论key是什么值,都会启动协程。
    • 当key变化时,只有当前Composable仍处于组合状态,才会取消旧协程并启动新协程。一旦页面被导航移除,协程会被自动取消,后续状态更新无法触发任何逻辑。

解决方案

方案1:在LaunchedEffect中过滤result状态

LaunchedEffect(key1 = state) {
  if (!state.result) return@LaunchedEffect // 仅当result为true时执行后续逻辑
  if (state.userExist) {
    Log.d("LaunchedEffect", "user exist")
    navController.navigate(AppRoutes.OVIDE_SCREEN) {
      popUpTo(AppRoutes.SPLASH_SCREEN) { inclusive = true }
    }
  } else {
    Log.d("LaunchedEffect", "user not exist")
    navController.navigate(AppRoutes.WELCOME_SCREEN) {
      popUpTo(AppRoutes.SPLASH_SCREEN) { inclusive = true }
    }
  }
}

方案2:在协程中collect状态直到result就绪

LaunchedEffect(Unit) {
  viewModel.state.collect { currentState ->
    if (currentState.result) {
      if (currentState.userExist) {
        Log.d("LaunchedEffect", "user exist")
        navController.navigate(AppRoutes.OVIDE_SCREEN) {
          popUpTo(AppRoutes.SPLASH_SCREEN) { inclusive = true }
        }
      } else {
        Log.d("LaunchedEffect", "user not exist")
        navController.navigate(AppRoutes.WELCOME_SCREEN) {
          popUpTo(AppRoutes.SPLASH_SCREEN) { inclusive = true }
        }
      }
      // 导航完成后停止collect,避免重复执行
      return@collect
    }
  }
}

方案3:优化ViewModel逻辑,避免初始状态触发无效逻辑

将result的初始值设为null,仅在检查完成后设为true,确保LaunchedEffect仅在检查完成后启动:

// 修改SplashUiState
data class SplashUiState(
  val result: Boolean? = null, // 初始为null,表示未完成检查
  val userExist: Boolean = false,
  val error: List<Pair<String, String>> = emptyList()
)

// SplashScreen中
LaunchedEffect(key1 = state.result) {
  state.result ?: return@LaunchedEffect // 未完成检查时不执行
  if (state.userExist) {
    // 导航逻辑
  } else {
    // 导航逻辑
  }
}

LaunchedEffect核心机制总结

  • 生命周期绑定:LaunchedEffect的协程与所在Composable的生命周期绑定,Composable离开组合时协程自动取消。
  • key触发逻辑:当key参数发生变化时,旧协程会被取消,新协程会立即启动。首次组合时,无论key值如何,都会启动协程。
  • 状态快照特性:协程启动时捕获的是当前Composable的状态快照,但如果在协程中持续观察状态(如collect),可以获取最新状态。

内容的提问来源于stack exchange,提问作者Kako

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 19:43:12