我有这个 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