使用导航退出当前可组合项时,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 会两次重新组合并显示成功状态 的相关文章

  • 在 Android 上通话结束时启动活动

    我想在通话结束时启动一项活动 找不到任何对此的参考 我该怎么做 我还没有尝试过这个 但我假设你可以运行一些服务 始终在后台运行 它利用电话状态监听器 http developer android com reference android
  • 我在布局上看不到任何 FirebaseRecyclerAdapter 项目

    我试图将数据从 Firebase 数据库检索到我的布局 但我看不到任何项目FirebaseRecyclerAdapter在布局中 请帮忙 我按照一个教程展示了如何做到这一点 当我运行应用程序时 我没有看到任何项目 但我可以滚动 public
  • Xamarin Android Webview Javascript

    我正在尝试通过 Xamarin for Android 创建一个移动应用程序 它有一个显示网站的 WebView 问题是正常按钮会触发 但 javascript 事件不会触发 我已经启用了 Javascript 但没有运气 如何在 Andr
  • 如何在android中显示保存在sdcard文件夹中的图像[关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 当我正在显
  • 如何正确释放Android MediaPlayer

    我正在尝试向我的 Android 应用程序添加一个按钮 当点击该按钮时它会播放 MP3 我已经让它工作了 但没有办法释放 mediaPlayer 对象 因此即使在我离开活动后它仍然会继续播放 如果我在react 方法之外初始化MediaPl
  • Android Q:file.mkdirs() 返回 false

    我们有一个应用程序 使用外部存储来存储一些临时文件 图像 二进制数据 该代码已经运行了几年 直到最近才发生重大变化 在 Android Q 上它不起作用 File f new File Environment getExternalStor
  • Gradle 构建错误:无法从 https://repo1.maven.org/maven2/io/fabric/tools/gradle/maven-metadata.xml 加载 Maven 元数据

    我在 Android studio 中遇到 gradle 构建错误 如下所示 Error A problem occurred configuring project MyApp Could not resolve all dependen
  • fetchUuidsWithSdp 的奇怪 UUID 逆转

    我有一个在树莓派上运行的 python 蓝牙服务器 使用 PyBluez 我在服务器中使用的uuid是 8f86d132 4ab8 4c15 b8df 0b70cf10ea56 我正在打电话device fetchUuidsWithSdp
  • 如何在 Linux 内核中定义并触发我自己的新软中断?

    我想在 Linux 内核中创建自己的软中断 这是正确的方法吗 In the init我想触发该模块的softirq我将添加一个调用 394 void open softirq int nr void action struct softir
  • 如何在我现有的 Android 应用程序中使用 Telegram API(包括聊天应用程序)?

    我想使用 telegram API 在我现有的 Android 应用程序中开发聊天功能 我不知道如何实施 我认为 看看Telegram 数据库库 测试版 从这里TDLib https core telegram org tdlib 俄语 但
  • 如何更改终端的默认目录?

    我想更改 Android Studio v2 2 2 终端的默认目录 当我打开终端时 它基于项目的目录 C 项目路径 我经常需要使用adb shell 所以我必须导航到 SDK 路径 平台工具 才能使用 adb 命令 是否可以更改终端的默认
  • java.lang.IllegalStateException:应用程序 PagerAdapter 更改了适配器的内容,而没有调用 PagerAdapter#notifyDataSetChanged android

    我正在尝试使用静态类将值传递给视图 而不是使用意图 因为我必须传递大量数据 有时我会收到此错误 但无法找出主要原因是什么 Error java lang IllegalStateException The application s Pag
  • Android 深度链接至 Instagram 应用

    Instagram 已经发布了 iOS 深层链接的 url 方案 但尚未为 Android 创建文档 有没有办法深入链接到 Android 上的 Instagram 应用程序 以转到 Instagram 应用程序中的特定位置 例如 Inst
  • 在 android 中建立与 MySQL 的池连接

    我需要从我的 Android 应用程序访问 MySQL 数据库 现在所有的工作都通过 DriverManager getConnection url 等等 但我必须从多个线程访问数据库 所以我必须使用连接池 问题1 是 com mysql
  • Android Eclipse 上的 Web 服务

    我是 android eclipse java 的新手 事实上这个论坛也是如此 有人遇到过这种情况吗 从用户那里获取输入并通过使用 android eclipse 中的 Web 服务来显示适当的结果 有可用的示例吗 非常感谢 我正在发布教程
  • Android 手机作为 GSM 调制解调器在 PC 上发送/接收短信?

    是否可以将 Android 移动设备用作 PC 上的 GSM 调制解调器 我正在 net下开发应用程序来发送 接收短信等 现在我想通过 USB 将我的 Android 设备连接到我的 PC 并将其用作 GSM 调制解调器来与其通信 这里是参
  • 图像作为电子邮件附件

    我想构建一个应用程序 我可以在电子邮件中附加图像 打开图像并将其设置为我的壁纸 我想让它跨平台 所以你能告诉我是否可以使用phonegap 或者我是否必须为iphone和android构建一个本机应用程序 您好 如果您只想通过电子邮件附加图
  • 使用片段时应用程序崩溃

    我正在处理碎片和 我的代码中有一个我找不到的问题 logcat 指向我的一个片段中的这段代码 Override public View onCreateView LayoutInflater inflater ViewGroup conta
  • 您使用什么物理 Android 设备进行测试?

    有什么好的推荐用于测试目的的物理 Android 设备吗 我正在苹果阵营寻找像 iPod touch 这样的设备 可以帮助 iOS 开发人员测试他们的东西 我知道有 Nexus One 但那东西相当昂贵 而且我并不真正关心手机的东西 而是可
  • 通过电子邮件发送文本文件附件

    我正在尝试附加一个文本文件以便通过电子邮件发送 但每当我打开电子邮件应用程序时 它都会说该文件不存在 请帮助 Intent i new Intent Intent ACTION SEND i setType text plain i put

随机推荐