【问题标题】:Jetpack Compose recompose with success state twice when exiting current ComposableJetpack Compose 在退出当前 Composable 时以成功状态重新组合两次
【发布时间】:2021-09-15 09:05:11
【问题描述】:

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

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

UiState 为

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

enum class Status {
    LOADING,
    SUCCESS,
    ERROR
}

然后它会获取 NavGraph 上的项目并收集状态

@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)
            }
        }
    }
}

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 输出中可以看出它工作正常

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 状态两次重组,这可能是什么原因?

I: ListScreen() uiState: SUCCESS
I: DetailScreen user: User(id=4, email=eve.holt@reqres.in, 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

【问题讨论】:

    标签: android android-jetpack-compose


    【解决方案1】:

    NavHost中保存navhostController为

    import androidx.compose.runtime.rememberUpdatedState
    .
    .
    
    NavHost(
        startDestination = "start_destination",
        modifier = Modifier.padding(paddingValues)
    ) {
      val navigationController by rememberUpdatedState(newValue = rememberNavController())
      .
      .
    

    并像使用它

    composable(route = "start_destination") {
         ListScreen(scaffoldState, uiState) { user ->
             navigationController.navigate("detail/${user.id}")
         }
     }
    

    【讨论】:

    • 我尝试使用带有rememberUpdatedState 的 navController 并且仍然得到相同的结果。还编辑了问题并添加了MainScreen 可组合。奇怪的是,当我从DetailScreen 返回时,它会重新组合LazyColumn 三次。我不知道不使用UiStateStateFlow 是否更好
    • 我认为由于您将 navController 作为参数发送NavHost,对 navController 的任何更改都会触发重组。我已经编辑了我的答案。你不能将 navController 作为参数传递给NavHost 吗?
    猜你喜欢
    • 1970-01-01
    • 2022-11-26
    • 2022-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-05
    • 1970-01-01
    相关资源
    最近更新 更多