StateFlow 在用于导航到其他可组合项时会多次触发

aku*_*ubi 5 android android-jetpack-navigation android-jetpack-compose kotlin-stateflow

我一直在使用 StateFlow + 密封接口来表示 Android 应用程序中的各种 UI 状态。在我的 ViewModel 中,我有一个密封接口UiState来执行此操作,并且各种状态公开为StateFlow

sealed interface UiState {
    class LocationFound(val location: CurrentLocation) : UiState
    object Loading : UiState
    // ...
    class Error(val message: String?) : UiState
}


@HiltViewModel
class MyViewModel @Inject constructor(private val getLocationUseCase: GetLocationUseCase): ViewModel() {

    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState

// ...
}
Run Code Online (Sandbox Code Playgroud)

然后在可组合项中,我以这种方式观察事件:

@Composable
fun MyScreen(
    viewModel: HomeScreenViewModel,
    onLocationFound: (CurrentLocation) -> Unit,
    onSnackbarButtonClick: () -> Unit
) {

// ...
    LaunchedEffect(true) { viewModel.getLocation() }
    when (val state = viewModel.uiState.collectAsState().value) {
        is UiState.LocationFound -> {
            Log.d(TAG, "MyScreen: LocationFound")
            onLocationFound.invoke(state.location)
        }
        UiState.Loading -> LoadingScreen
        // ...
    }

}
Run Code Online (Sandbox Code Playgroud)

在我的 MainActivity.kt 中,当onLocationFound调用回调时,我应该导航到Screen2以下位置中的另一个目的地 ( ) NavGraph

enum class Screens {
    Screen1,
    Screen2,
   // ...
}

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val navController = rememberNavController()
            MyTheme {
                MyNavHost(navController = navController)
            }
        }
    }
}

@Composable
fun MyNavHost(navController: NavHostController) {
    val context = LocalContext.current
    NavHost(navController = navController, startDestination = Screens.Home.name) {
        composable(Screens.Screen1.name) {
            val viewModel = hiltViewModel<MyViewModel>()
            MyScreen(viewModel = viewModel, onLocationFound = {
                navController.navigate(
                    "${Screens.Screen2.name}/${it.locationName}/${it.latitude}/${it.longitude}"
                )
            }, onSnackbarButtonClick = { // ... }
            )
        }
        // ....
        composable("${Screens.Screen2.name}/{location}/{latitude}/{longitude}", arguments = listOf(
            navArgument("location") { type = NavType.StringType },
            navArgument("latitude") { type = NavType.StringType },
            navArgument("longitude") { type = NavType.StringType }
        )) {
            // ... 
        }
    }
}

Run Code Online (Sandbox Code Playgroud)

但发生的情况是,onLocationFound回调似乎被击中了多次,因为我可以看到我放置的日志记录在 Logcat 中多次显示,因此我多次导航到同一位置,导致屏幕出现烦人的闪烁。我查了一下 MyViewmodel,我绝对不会设置_uiState.value = LocationFound多次。奇怪的是,当我用 包装回调的调用时LaunchedEffect(true)LocationFound只被调用两次,这仍然很奇怪,但至少没有闪烁。

但仍然LocationFound应该只被调用一次。我有一种感觉,这里正在发挥重构或一些有关 Compose 导航的警告,但我已经研究过,但找不到合适的术语来寻找。