Android Jetpack Compose:保存数据后ScreenLearn页面无显示问题
问题描述
我有一个名为ScreenPlay的页面,其中的卡片带有保存选项。通过Logcat确认数据已成功保存,但导航至ScreenLearn页面时,页面为空,没有显示任何内容。以下是我的相关代码:
ScreenPlay.kt
@Composable fun ScreenPlay(navController: NavController){ // Mutable state to hold the dynamically updated saved cards var savedCards by remember { mutableStateOf(emptyList<SavedCard>()) } lateinit var playList:List<ImageModel> playList = ArrayList() playList = listOf( ImageModel("Tejas",R.drawable.television,R.drawable.save), ImageModel("Nikita",R.drawable.food,R.drawable.save), ImageModel("xyz",R.drawable.dish,R.drawable.save), ImageModel("gggg",R.drawable.bird,R.drawable.save) ) val context = LocalContext.current val cardViewmodel: ScreenViewModel = viewModel( factory = ScreenViewmodelFactory((context.applicationContext as MyApp).repository) ) // val navController = rememberNavController() //For using the object on ArrowBack Icon val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher Scaffold(topBar = { TopAppBar(title = { Text(text = "Days") }, navigationIcon = { IconButton(onClick = { navController.popBackStack() }) { Icon( Icons.Default.ArrowBack, contentDescription = "Back", modifier = Modifier .padding(16.dp) .clickable { onBackPressedDispatcher?.onBackPressed() } ) } } ) }, floatingActionButton = { FloatingActionButton(onClick = { navController.navigate(Destinations.ScreenLearn) }, modifier = Modifier.padding(bottom = 50.dp, end = 40.dp) ) { Icon( Icons.Default.Add, contentDescription = "Save" ) } }, floatingActionButtonPosition = FabPosition.End ) { LazyColumn( modifier = Modifier .padding(10.dp) .padding(bottom = 56.dp) .navigationBarsPadding() ) { items(playList.size) { Card( shape = RoundedCornerShape(20.dp), modifier = Modifier .padding(8.dp) .height(150.dp), elevation = 6.dp, ) { Box{ Column( Modifier .fillMaxSize() .padding(5.dp) .background(Color(153, 50, 204)) .clip(RoundedCornerShape(20.dp)), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { Image( painter = painterResource(id = playList[it].imageId), contentDescription = "Images" ) Spacer(modifier = Modifier.padding(bottom = 5.dp)) Text( text = playList[it].imageName, color = Color.White, fontWeight = FontWeight.Bold ) } Image(painter = painterResource(id = playList[it].imageSaved), contentDescription = "Save data", modifier = Modifier .padding(8.dp) .padding(top = 10.dp) .align(Alignment.TopEnd) .clickable { val imagename = playList[it].imageName val imageResource = playList[it].imageId val newSaved = SavedCard(imageId =imageResource, imageName = imagename) savedCards += newSaved cardViewmodel.insertCards(newSaved) Log.d("Database", "Value saved: $newSaved") Toast.makeText(context,"Image saved", Toast.LENGTH_SHORT).show() } ) } } } } } }
ScreenLearn.kt
@SuppressLint("UnusedMaterialScaffoldPaddingParameter") @Composable fun ScreenLearn(savedCards: List<SavedCard>) { Scaffold( topBar = { TopAppBar(title = { Text(text = "Screen Learn") }) } ) { LazyColumn(modifier = Modifier.padding(16.dp)) { items(savedCards) { savedCard -> Card( modifier = Modifier .fillMaxWidth() .padding(bottom = 16.dp), elevation = 4.dp, shape = RoundedCornerShape(8.dp) ) { Column(modifier = Modifier.padding(16.dp)) { Text(text = savedCard.imageName, style = MaterialTheme.typography.h6) Image( painter = painterResource(id = savedCard.imageId), contentDescription = savedCard.imageName, modifier = Modifier .fillMaxWidth() .padding(top = 8.dp) .height(200.dp) .clip(shape = RoundedCornerShape(8.dp)) .background(Color.LightGray) ) } } } } } }
ScreenViewModel.kt
class ScreenViewModel(private val savedCardRepository:SavedCardRepository):ViewModel() { // Using LiveData and caching what allWords returns has several benefits: // - We can put an observer on the data (instead of polling for changes) and only update the // the UI when the data actually changes. // - Repository is completely separated from the UI through the ViewModel. var allWords: LiveData<List<SavedCard>> = savedCardRepository.savedCards.asLiveData() /** * Launching a new coroutine to insert the data in a non-blocking way */ fun insertCards(savedCard: SavedCard) = viewModelScope.launch { savedCardRepository.saveCard(savedCard) } fun deleteCards(savedCard: SavedCard) = viewModelScope.launch { savedCardRepository.deleteCard(savedCard) } } class ScreenViewmodelFactory(private val savedCardRepository:SavedCardRepository) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(ScreenViewModel::class.java)) { @Suppress("UNCHECKED_CAST") return ScreenViewModel(savedCardRepository) as T } throw IllegalArgumentException("Unknown ViewModel class") } }
App.kt(导航配置)
@Composable fun App() { val navController = rememberNavController() val items = listOf<String>("User","Play") val selectedItem = remember { mutableStateOf(0) } val savedCards = remember { mutableStateListOf<SavedCard>() } Scaffold(bottomBar = { BottomNavigation { items.forEachIndexed { index, item -> BottomNavigationItem(selected = selectedItem.value == index, onClick = { selectedItem.value = index when(index){ 0->navController.navigate(Destinations.ScreenA) 1->navController.navigate(Destinations.ScreenPlay) } }, icon = { if (index==0){ Icon(Icons.Default.Person, contentDescription = item) }else{ Icon(Icons.Default.PlayArrow, contentDescription = item) } }, label = { Text(text = item) } ) } } } ) { NavHost(navController = navController, startDestination = Destinations.ScreenA) { composable(ScreenA) { ScreenA(navController) } composable(ScreenB) { ScreenB(navController) } composable(ScreenC) { ScreenC(navController) } composable(Destinations.ScreenPlay){ ScreenPlay(navController) } composable(Destinations.ScreenLearn){ ScreenLearn(savedCards = savedCards) } } } }
排查与修复指导
核心问题
代码存在两处数据同步断层,导致ScreenLearn无法获取已保存的数据库数据:
问题1:ScreenPlay本地状态无效且未同步
ScreenPlay里维护的本地savedCards状态,既没和ViewModel的数据库数据关联,也没同步到App.kt传给ScreenLearn的列表,属于冗余无效代码。
修复:
- 删除ScreenPlay中的本地
savedCards状态:
// 移除这一行 var savedCards by remember { mutableStateOf(emptyList<SavedCard>()) }
- 删除保存点击事件中对本地状态的更新:
// 移除这一行 savedCards += newSaved
问题2:ScreenLearn未订阅数据库数据
当前ScreenLearn接收的是App.kt初始化的空列表,这个列表从未从数据库拉取数据,所以一直为空。
修复:
- 修改ScreenLearn,让它通过ViewModel获取数据库数据:
@SuppressLint("UnusedMaterialScaffoldPaddingParameter") @Composable fun ScreenLearn(navController: NavController) { val context = LocalContext.current val cardViewmodel: ScreenViewModel = viewModel( factory = ScreenViewmodelFactory((context.applicationContext as MyApp).repository) ) // 订阅ViewModel中的LiveData数据 val savedCards by cardViewmodel.allWords.observeAsState(emptyList()) Scaffold( topBar = { TopAppBar(title = { Text(text = "Screen Learn") }) } ) { padding -> LazyColumn(modifier = Modifier.padding(padding).padding(16.dp)) { items(savedCards) { savedCard -> Card( modifier = Modifier .fillMaxWidth() .padding(bottom = 16.dp), elevation = 4.dp, shape = RoundedCornerShape(8.dp) ) { Column(modifier = Modifier.padding(16.dp)) { Text(text = savedCard.imageName, style = MaterialTheme.typography.h6) Image( painter = painterResource(id = savedCard.imageId), contentDescription = savedCard.imageName, modifier = Modifier .fillMaxWidth() .padding(top = 8.dp) .height(200.dp) .clip(shape = RoundedCornerShape(8.dp)) .background(Color.LightGray) ) } } } } } }
- 更新App.kt中的导航配置,移除无用的
savedCards状态并修改ScreenLearn的调用:
@Composable fun App() { val navController = rememberNavController() val items = listOf<String>("User","Play") val selectedItem = remember { mutableStateOf(0) } Scaffold(bottomBar = { BottomNavigation { items.forEachIndexed { index, item -> BottomNavigationItem(selected = selectedItem.value == index, onClick = { selectedItem.value = index when(index){ 0->navController.navigate(Destinations.ScreenA) 1->navController.navigate(Destinations.ScreenPlay) } }, icon = { if (index==0){ Icon(Icons.Default.Person, contentDescription = item) }else{ Icon(Icons.Default.PlayArrow, contentDescription = item) } }, label = { Text(text = item) } ) } } } ) { padding -> NavHost(navController = navController, startDestination = Destinations.ScreenA, modifier = Modifier.padding(padding)) { composable(ScreenA) { ScreenA(navController) } composable(ScreenB) { ScreenB(navController) } composable(ScreenC) { ScreenC(navController) } composable(Destinations.ScreenPlay){ ScreenPlay(navController) } composable(Destinations.ScreenLearn){ ScreenLearn(navController = navController) } } } }
(注:顺便修复了Scaffold的padding未使用的问题)
额外验证点
- 确认
SavedCardRepository中的savedCards是正确的Flow(比如Room的@Query("SELECT * FROM saved_cards") fun getSavedCards(): Flow<List<SavedCard>>),这样ViewModel的allWords才能实时感知数据库变化。 - 可以给ScreenLearn添加空状态提示:
LazyColumn(modifier = Modifier.padding(padding).padding(16.dp)) { if (savedCards.isEmpty()) { item { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text(text = "暂无保存内容") } } } else { items(savedCards) { savedCard -> // 原有卡片代码 } } }
内容的提问来源于stack exchange,提问作者PurpleRabbit
相关产品推荐
相关产品推荐

