Android中如何持久化保存用户输入的山羊信息?
山羊数据持久化实现方案
要实现应用重启后保留山羊数据,你需要完成以下核心改造:
一、调整数据模型支持序列化
你的Goats类需要能被序列化,才能保存到本地存储。同时建议把姓名、品种从字符串资源ID改为普通字符串,适配用户自定义输入:
data class Goats( val name: String, val breed: String, val age: Int, val additionalInfo: String = "", val imageUri: String = "" ) : Serializable
二、改造ViewModel管理数据与持久化
把数据从Composable的remember移到ViewModel,用SharedPreferences作为入门级持久化方案(适合小体量数据):
class MainViewModel : ViewModel() { private val sharedPrefs = ApplicationProvider.getApplicationContext<Context>().getSharedPreferences("GoatsPrefs", Context.MODE_PRIVATE) val goats = mutableStateListOf<Goats>() val bucks = mutableStateListOf<Goats>() var isDialogShown by mutableStateOf(false) init { loadSavedData() // 启动时加载本地数据 } // 保存数据到本地 private fun saveData() { val editor = sharedPrefs.edit() val gson = Gson() // 序列化山羊列表 editor.putString("goats", gson.toJson(goats)) editor.putString("bucks", gson.toJson(bucks)) editor.apply() } // 从本地加载数据 private fun loadSavedData() { val gson = Gson() val goatsJson = sharedPrefs.getString("goats", null) val bucksJson = sharedPrefs.getString("bucks", null) // 加载Does数据,没有则初始化默认值 goatsJson?.let { val type = object : TypeToken<MutableList<Goats>>() {}.type goats.addAll(gson.fromJson(it, type)) } ?: run { goats.addAll(listOf( Goats("Red", "Alpine", 7), Goats("Little Goat", "Alpine", 8), Goats("Nana", "Alpine", 7), Goats("Sharkie", "Alpine", 10) )) } // 加载Bucks数据,没有则初始化默认值 bucksJson?.let { val type = object : TypeToken<MutableList<Goats>>() {}.type bucks.addAll(gson.fromJson(it, type)) } ?: run { bucks.add(Goats("Billy", "Mix", 7)) } } // 添加新山羊并保存 fun addGoat(goat: Goats) { goats.add(goat) saveData() } fun addBuck(buck: Goats) { bucks.add(buck) saveData() } // 更新山羊信息并保存 fun updateGoat(index: Int, updatedGoat: Goats) { if (index in goats.indices) { goats[index] = updatedGoat saveData() } } fun updateBuck(index: Int, updatedBuck: Goats) { if (index in bucks.indices) { bucks[index] = updatedBuck saveData() } } fun isGoatClick() { isDialogShown = true } fun onDismissDialog() { isDialogShown = false } }
需在Module的build.gradle中添加Gson依赖:implementation 'com.google.code.gson:gson:2.10.1'
三、修改Composable代码,绑定ViewModel数据
1. MainActivity中正确获取ViewModel实例
class MainActivity : ComponentActivity() { private val viewModel by viewModels<MainViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { GoatsTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { GoatsApp(viewModel = viewModel) } } } } }
2. 修改GoatsApp,使用ViewModel的列表数据
@Composable fun GoatsApp(viewModel: MainViewModel) { val provider = GoogleFont.Provider( providerAuthority = "com.google.android.gms.fonts", providerPackage = "com.google.android.gms", certificates = R.array.com_google_android_gms_fonts_certs ) val fontName = GoogleFont("Lato") val fontFamily = FontFamily(Font(googleFont = fontName, fontProvider = provider)) Column(modifier = Modifier .padding(16.dp) .fillMaxHeight(0.90f)) { // Does区域 Row( modifier = Modifier .fillMaxWidth(0.95f) .border(3.dp, Color.Gray, RoundedCornerShape(13.dp)) ) { Button( onClick = { viewModel.addGoat(Goats("Little Goat", "Alpine", 9)) }, shape = RoundedCornerShape(10.dp), modifier = Modifier.background(Color.LightGray) ) { Text(text = "+") } Text( text = "Does", modifier = Modifier .background(Color.LightGray) .fillMaxWidth() .padding(12.dp), fontStyle = FontStyle.Italic, textAlign = TextAlign.Center, fontFamily = fontFamily, fontWeight = FontWeight.ExtraLight ) } GoatList(modifier = Modifier.padding(8.dp), goats = viewModel.goats, viewModel = viewModel) // Bucks区域 Row( modifier = Modifier .fillMaxWidth(0.95f) .border(3.dp, Color.Gray, RoundedCornerShape(13.dp)) ) { Button( onClick = { viewModel.addBuck(Goats("Billy", "Mix", 9)) }, shape = RoundedCornerShape(10.dp), modifier = Modifier.background(Color.LightGray) ) { Text(text = "+") } Text( text = "Bucks", modifier = Modifier .background(Color.LightGray) .fillMaxWidth() .padding(12.dp), fontStyle = FontStyle.Italic, textAlign = TextAlign.Center, fontFamily = fontFamily, fontWeight = FontWeight.ExtraLight ) } BuckList(modifier = Modifier.padding(8.dp), bucks = viewModel.bucks, viewModel = viewModel) } }
3. 改造列表组件,传递索引和ViewModel
@Composable fun GoatList(modifier: Modifier = Modifier, goats: List<Goats>, viewModel: MainViewModel) { LazyVerticalGrid(columns = GridCells.Fixed(1), modifier.padding(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { itemsIndexed(goats) { index, goat -> GoatCard(allGoats = goat, index = index, isBuck = false, viewModel = viewModel) } } } @Composable fun BuckList(modifier: Modifier = Modifier, bucks: List<Goats>, viewModel: MainViewModel) { LazyVerticalGrid(columns = GridCells.Fixed(1), modifier.padding(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { itemsIndexed(bucks) { index, buck -> GoatCard(allGoats = buck, index = index, isBuck = true, viewModel = viewModel) } } }
4. 修改GoatCard,弹窗关闭时更新数据
@Composable fun GoatCard(allGoats: Goats, index: Int, isBuck: Boolean, viewModel: MainViewModel) { var stateAdditional by rememberSaveable { mutableStateOf(allGoats.additionalInfo) } var stateName by rememberSaveable { mutableStateOf(allGoats.name) } var stateAge by rememberSaveable { mutableStateOf(allGoats.age.toString()) } val imageUri = rememberSaveable { mutableStateOf(allGoats.imageUri) } val myImage = rememberAsyncImagePainter( imageUri.value.ifEmpty { R.drawable.pexels_anna_shvets_5953826 } ) val launcher = rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) { uri: Uri? -> uri?.let { imageUri.value = it.toString() } } Card(modifier = Modifier) { Row { Image( painter = myImage, contentDescription = stateName, modifier = Modifier.size(68.dp), contentScale = ContentScale.Crop ) Column(Modifier.width(120.dp)) { Text( text = stateName, modifier = Modifier.padding(8.dp), style = MaterialTheme.typography.titleSmall ) Row { Text( text = allGoats.breed, modifier = Modifier.padding(8.dp), style = MaterialTheme.typography.bodySmall ) Text( stateAge, modifier = Modifier.padding(8.dp), style = MaterialTheme.typography.bodySmall ) Box { Button( onClick = { viewModel.isGoatClick() }, modifier = Modifier.fillMaxWidth(), colors = ButtonDefaults.buttonColors(Color.LightGray) ) {} Text( text = "More", style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.End, fontWeight = FontWeight.Bold, modifier = Modifier .fillMaxWidth() .padding(end = 6.dp, top = 12.dp) ) } } if (viewModel.isDialogShown) { CustomDialog( onDismiss = { viewModel.onDismissDialog() // 解析年龄并更新数据 val age = stateAge.toIntOrNull() ?: allGoats.age val updatedGoat = allGoats.copy( name = stateName, age = age, additionalInfo = stateAdditional, imageUri = imageUri.value ) if (isBuck) viewModel.updateBuck(index, updatedGoat) else viewModel.updateGoat(index, updatedGoat) }, myGoats = allGoats, onValueChange = { stateName = it }, value = stateName, onAgeValueChange = { stateAge = it }, ageValue = stateAge, launcher = launcher, myImage = myImage, addValue = stateAdditional, addOnValueChange = { stateAdditional = it }, ) } } } } }
四、进阶方案:Room数据库
如果后续数据量增大或需要复杂查询,推荐使用Android官方的Room数据库,它更适合结构化数据的持久化。可以先掌握SharedPreferences方案,再逐步过渡。
内容的提问来源于stack exchange,提问作者vukica
相关产品推荐
相关产品推荐

