使用导航退出当前可组合项时,Jetpack Compose 会两次重新组合并显示成功状态

2023-12-04

我有这个 ViewModel,它在以下命令的帮助下从 api 返回用户并捆绑加载、错误和成功状态UiState<T>数据类为

class UsersViewModel : ViewModel() {
    private val _state = MutableStateFlow<UiState<List<User>>>(UiState(status = Status.LOADING))
    val state: StateFlow<UiState<List<User>>>
        get() = _state

    private val usersUseCase by lazy {
        UsersUseCase(UsersRepository(getUserApi()))
    }

    fun fetchUsers(page: Int = 1) {
        println(" UsersViewModel fetchUsers() page: $page")
        viewModelScope.launch {
            try {
                _state.value = UiState(status = Status.LOADING)
                val users = usersUseCase.getUserList(page)
                _state.value = UiState(status = Status.SUCCESS, data = users)
                println(" UsersViewModel fetchUsers() SUCCESS")
            } catch (e: Exception) {
                _state.value = UiState(status = Status.ERROR, error = e)
            }
        }
    }
}

Ui状态为

data class UiState<T>(
    val status: Status,
    val data: T? = null,
    val error: Throwable? = null
)

enum class Status {
    LOADING,
    SUCCESS,
    ERROR
}

然后它会获取 NavGraph 上的项目并使用 collet 状态

@Composable
fun MainScreen() {
    println("MainScreen()")
    val scaffoldState = rememberScaffoldState()
    val navController = rememberNavController()

    Scaffold(
        scaffoldState = scaffoldState,
        topBar = {
            TopAppBar(
                title = { Text("EffectHandlers") },
                navigationIcon = {
                    IconButton(onClick = {}) {
                        Icon(Icons.Default.Menu, "Menu")
                    }
                },
                actions = {
                    IconButton(onClick = { /* doSomething() */ }) {
                        Icon(Icons.Filled.Chat, contentDescription = null)
                    }

                    IconButton(onClick = { /* doSomething() */ }) {
                        Icon(Icons.Filled.ChatBubble, contentDescription = null)
                    }
                }
            )
        }
    ) { paddingValues ->
        NavGraph(navController, scaffoldState, paddingValues)
    }
}

@Composable
private fun NavGraph(
    navController: NavHostController,
    scaffoldState: ScaffoldState,
    paddingValues: PaddingValues
) {

    val userViewModel by remember { mutableStateOf(UsersViewModel()) }
    val uiState: UiState<List<User>> by userViewModel.state.collectAsState()

    userViewModel.fetchUsers(1)

    NavHost(
        navController = navController,
        startDestination = "start_destination",
        modifier = Modifier.padding(paddingValues)
    ) {
        composable(route = "start_destination") {
            ListScreen(scaffoldState, uiState) { user ->
                navController.navigate("detail/${user.id}")
            }
        }

        composable(route = "detail/{userId}", arguments = listOf(
            navArgument("userId") {
                type = NavType.StringType
            }
        )) { backStackEntry ->

            val arguments = requireNotNull(backStackEntry.arguments)
            val userId = arguments.getString("userId")
            val user = uiState.data?.find {
                it.id.toString() == userId
            }

            user?.let {
                DetailScreen(user = user)
            }
        }
    }
}

And CircularProgressIndicator处于加载状态时显示,成功状态下显示的项目和错误状态下显示的snackbar。

@Composable
private fun ListScreen(
    scaffoldState: ScaffoldState,
    uiState: UiState<List<User>>,
    onUserClicked: (User) -> Unit
) {

    println("ListScreen() uiState: ${uiState.status}")

    when (uiState.status) {

        Status.SUCCESS -> {
            val users = requireNotNull(uiState.data)
            UserList(users, onUserClicked)
        }

        Status.LOADING -> {
            Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                CircularProgressIndicator()
            }
        }

        Status.ERROR -> {
            // `LaunchedEffect` will cancel and re-launch if
            // `scaffoldState.snackbarHostState` changes
            LaunchedEffect(scaffoldState.snackbarHostState) {
                // Show snackbar using a coroutine, when the coroutine is cancelled the
                // snackbar will automatically dismiss. This coroutine will cancel whenever
                // `state.hasError` is false, and only start when `state.hasError` is true
                // (due to the above if-check), or if `scaffoldState.snackbarHostState` changes.
                scaffoldState.snackbarHostState.showSnackbar(
                    message = "Error message",
                    actionLabel = "Retry message"
                )
            }
        }
    }
}


@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun UserList(
    users: List<User>,
    onUserClicked: (User) -> Unit
) {

    LazyColumn() {
        items(users) { user ->

            println("UserList() LazyColumn user: ${user.id}")

            ListItem(
                modifier = Modifier
                    .fillMaxWidth()
                    .clickable {
                        onUserClicked(user)
                    },
                icon = {
                    Image(
                        modifier = Modifier
                            .size(50.dp)
                            .clip(CircleShape),
                        painter = rememberImagePainter(data = user.avatar),
                        contentDescription = null
                    )
                },
                overlineText = {
                    Text("${user.first_name} ${user.last_name}")
                },
                text = {
                    Text(user.email)
                }
            )
        }
    }
}

正如预期的那样,它工作得很好,可以看出println outputs

I: MainScreen()
I: UsersViewModel fetchUsers() page: 1
I: ListScreen() uiState: LOADING
I: ListScreen() uiState: LOADING
I: UsersViewModel fetchUsers() SUCCESS
I: ListScreen() uiState: SUCCESS
I: UserList() LazyColumn user: 1
I: UserList() LazyColumn user: 2
I: UserList() LazyColumn user: 3
I: UserList() LazyColumn user: 4
I: UserList() LazyColumn user: 5
I: UserList() LazyColumn user: 6

但是,当我单击某个项目进入详细信息屏幕时,我看到列表屏幕正在重新组合SUCCESS state twice,这可能是什么原因?

I: ListScreen() uiState: SUCCESS
I: DetailScreen user: User(id=4, [email protected], first_name=Eve, last_name=Holt, avatar=https://reqres.in/img/faces/4-image.jpg)
I: UserList() LazyColumn user: 1
I: UserList() LazyColumn user: 2
I: UserList() LazyColumn user: 3
I: UserList() LazyColumn user: 4
I: UserList() LazyColumn user: 5
I: UserList() LazyColumn user: 6
I: ListScreen() uiState: SUCCESS
I: UserList() LazyColumn user: 1
I: UserList() LazyColumn user: 2
I: UserList() LazyColumn user: 3
I: UserList() LazyColumn user: 4
I: UserList() LazyColumn user: 5
I: UserList() LazyColumn user: 6

发生这种情况是因为 NavHost 被组合并随后重新组合,如下所示这个问题也可以是多次。自从我打电话以来

userViewModel.fetchUsers(1)

它被调用两次。

执行一次性操作的正确做法是将代码包装为LaunchedEffect

LaunchedEffect(key=some key unique to condition) {

     userViewModel.fetchUsers(1)

}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用导航退出当前可组合项时,Jetpack Compose 会两次重新组合并显示成功状态 的相关文章

随机推荐