我正在开发一个 Android 应用程序,其中有一个底部栏。我正在使用 Jetpack Compose、hilt 和 ViewModel。我有一个 ViewModel (ShopListHomeViewModel) 来管理屏幕 (ShopListHomeScreen) 的状态。但是,当我切换选项卡并返回屏幕时,ViewModel 的状态会重置,导致我丢失数据并且屏幕再次加载。
你们能帮我看看遗漏和错误的地方吗?即使用户切换选项卡,我也想保留数据,因为我不想每次都加载,当我仅通过拉动刷新并创建新列表时,我会这样做。我正在添加代码和视频。谢谢
代码:
data class ShopListHomeState(
val isLoading: Boolean = true,
val name: String = "Test",
val surname: String = "User"
)
@HiltViewModel
internal class ShopListHomeViewModel @Inject constructor() : ViewModel() {
private val _state = MutableStateFlow(ShopListHomeState())
val state: StateFlow<ShopListHomeState> = _state.asStateFlow()
var counter: Int = 0
init {
viewModelScope.launch {
delay(3000)
_state.update { it.copy(isLoading = false) }
}
}
fun handleEvent(event: ShopListHomeEvents) {
viewModelScope.launch {
when (event) {
is ShopListHomeEvents.CreateNewListClicked -> {
_state.update { it.copy(name = counter++.toString()) }
}
}
}
}
}
和屏幕本身:
@Composable
internal fun ShopListHomeScreen() {
val viewModel: ShopListHomeViewModel = hiltViewModel()
val state = viewModel.state.collectAsState()
if (state.value.isLoading) {
ScLoadingScreen()
} else {
ShopListHomeScreenContent(state.value, viewModel)
}
}
@Composable
internal fun ShopListHomeScreenContent(
state: ShopListHomeState,
viewModel: ShopListHomeViewModel
) {
Column(modifier = Modifier.padding(16.dp)) {
ScCardItem(
modifier = Modifier.fillMaxWidth(),
onClick = {}
) {
Box(
modifier = Modifier
.padding(32.dp)
.fillMaxWidth(),
contentAlignment = Alignment.TopStart,
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement
.spacedBy(
space = 16.dp,
alignment = Alignment.Start
),
) {
Text(text = "Family List", style = ScTheme.typography.titleMedium)
Text(text = "Created by ${state.name}", style = ScTheme.typography.bodySmall)
}
}
}
Spacer(modifier = Modifier.weight(1f))
ScPrimaryButton(text = "Create New List", onClick = {
viewModel.handleEvent(ShopListHomeEvents.CreateNewListClicked)
})
}
}
有关导航和设置的其他代码:
@Composable
fun AppNavHost(
modifier: Modifier = Modifier,
navController: NavHostController,
startDestination: String = NavigationItem.Home.route,
) {
NavHost(
modifier = modifier,
navController = navController,
startDestination = startDestination,
enterTransition = {
EnterTransition.None
},
exitTransition = {
ExitTransition.None
}
) {
composable(NavigationItem.Home.route) {
ShopListHomeScreen()
}
composable(NavigationItem.Settings.route) {
SettingsHomeScreen()
}
composable(NavigationItem.CreateListScreen.route) {
ShopListCreateScreen(navController)
}
}
}
@Composable
internal fun SettingsHomeScreen() {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = "Setting List", style = ScTheme.typography.titleMedium)
Spacer(modifier = Modifier.weight(1f))
}
}
enum class Screen {
Home,
Settings,
CreateListScreen
}
sealed class NavigationItem(val route: String) {
object Home : NavigationItem(Screen.Home.name)
object Settings : NavigationItem(Screen.Settings.name)
object CreateListScreen : NavigationItem(Screen.CreateListScreen.name)
}
@Composable
fun TabView(tabBarItems: List<TabBarItem>, navController: NavController) {
var selectedTabIndex by rememberSaveable {
mutableStateOf(0)
}
NavigationBar {
tabBarItems.forEachIndexed { index, tabBarItem ->
NavigationBarItem(
colors = NavigationBarItemColors(
selectedIconColor= ScTheme.colors.neutrals.black200TextPrimary,
selectedTextColor = ScTheme.colors.neutrals.black200TextPrimary,
selectedIndicatorColor = Color.Transparent,
unselectedIconColor = ScTheme.colors.neutrals.grey200TitleSecondary,
unselectedTextColor = ScTheme.colors.neutrals.grey200TitleSecondary,
disabledIconColor = ScTheme.colors.neutrals.grey200TitleSecondary,
disabledTextColor = ScTheme.colors.neutrals.grey200TitleSecondary,
),
selected = selectedTabIndex == index,
onClick = {
selectedTabIndex = index
navController.navigate(tabBarItem.title)
},
icon = {
TabBarIconView(
isSelected = selectedTabIndex == index,
selectedIcon = tabBarItem.selectedIcon,
unselectedIcon = tabBarItem.unselectedIcon,
title = tabBarItem.title,
)
},
label = { Text(tabBarItem.title) })
}
}
}
@Composable
fun TabBarIconView(
isSelected: Boolean,
selectedIcon: ImageVector,
unselectedIcon: ImageVector,
title: String,
badgeAmount: Int? = null
) {
BadgedBox(badge = { TabBarBadgeView(badgeAmount) }) {
Icon(
imageVector = if (isSelected) {
selectedIcon
} else {
unselectedIcon
},
contentDescription = title
)
}
}
我认为您只是在
@Singleton
视图模型顶部缺少一个 ShopListHomeViewModel
标签,以确保您始终获得该类的单个实例。
或者,如果您有一个
@Provider
模块类或类似的东西来管理视图模型,您可以确保再次手动处理其中的单个实例,以便您始终从 DI 获取相同的对象。