【发布时间】: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