如何实现Jetpack Compose输入数据异步暂存后存入MongoDB?
Jetpack Compose 实现异步暂存数据后延迟写入MongoDB需求
我需要在Jetpack Compose中实现以下功能:
- 创建姓名和电话号码两个
TextField - 用户输入信息并点击确认按钮后,先异步暂存数据
- 导航到仅显示“hello”的主屏幕后,再将数据保存到MongoDB
以下是我当前的代码,请提供实现方案和优化反馈:
输入界面代码
Column( modifier = Modifier .fillMaxSize() .weight(1f), verticalArrangement = Arrangement.Top, horizontalAlignment = Alignment.CenterHorizontally ) { OutlinedTextField( modifier = Modifier .fillMaxWidth(0.80f), value = name, onValueChange = onNameChanged, colors = OutlinedTextFieldDefaults.colors( focusedBorderColor = LightBlue, ), label = { Text( text = "Name", color = LightBlue, fontWeight = FontWeight.Bold ) }, placeholder = { Text( text = "Enter your name", color = Color.Black ) }, singleLine = true, leadingIcon = { Icon( imageVector = Icons.Default.Person, contentDescription = null ) }, ) Spacer(modifier = Modifier.height(10.dp)) OutlinedTextField( modifier = Modifier.fillMaxWidth(0.8f), value = phoneNumber, onValueChange = { phoneNumber = it }, keyboardOptions = KeyboardOptions.Default.copy( keyboardType = KeyboardType.Number, imeAction = ImeAction.Done ), keyboardActions = KeyboardActions( onDone = { keyboardController?.hide() } ), singleLine = true, textStyle = MaterialTheme.typography.bodyMedium.copy(fontSize = 16.sp), colors = OutlinedTextFieldDefaults.colors( focusedBorderColor = Color.Blue, unfocusedBorderColor = Color.Gray ), label = { Text( text = "Phone Number", color = LightBlue, fontWeight = FontWeight.Bold ) }, placeholder = { Text(text = "Enter your telephone number") }, leadingIcon = { Icon( imageVector = Icons.Default.Phone, contentDescription = null ) }, ) Spacer(modifier = Modifier.height(10.dp)) Button( modifier = Modifier .wrapContentSize() .fillMaxWidth(0.8f) .height(80.dp) .padding(start = 20.dp, end = 20.dp, bottom = 40.dp) .align(Alignment.CenterHorizontally), colors = ButtonDefaults.buttonColors(ConfirmButtonColor), shape = RoundedCornerShape(10.dp), onClick = { if (name.isNotEmpty() && phoneNumber.isNotEmpty()) { navController.navigate(AuthScreen.GenderProfile.route) } else { Toast.makeText(context, "Not all fields are filled-in!", Toast.LENGTH_SHORT).show() } }, ) { Text( text = "Confirm", color = Color.White, ) }
Person类
class Person : RealmObject { @PrimaryKey var _id: ObjectId = ObjectId.invoke() var owner_id: String = "" // Profile var name: String = "" var phoneNumber: Int = 0 }
MongoRepository接口
interface MongoRepository { fun configureTheRealm() fun getData(): Flow<List<Person>> suspend fun insertPerson(person: Person) suspend fun updatePerson(person: Person) }
MongoDB实现类
object MongoDB : MongoRepository { private val app = App.create(APP_ID) private val user = app.currentUser private lateinit var realm: Realm init { configureTheRealm() } override fun configureTheRealm() { if (user != null) { val config = SyncConfiguration.Builder( user, setOf(Person::class) ) .initialSubscriptions { sub -> add(query = sub.query<Person>(query = "owner_id == $0", user.id)) } .log(LogLevel.ALL) .build() realm = Realm.open(config) } } override fun getData(): Flow<List<Person>> { return realm.query<Person>().asFlow().map { it.list } } override suspend fun insertPerson(person: Person) { if (user != null) { realm.write { try { copyToRealm(person.apply { owner_id = user.id }) } catch (e: Exception) { Log.d("MongoRepository", e.message.toString()) } } } } override suspend fun updatePerson(person: Person) { realm.write { val queriedPerson = query<Person>(query = "_id == $0", person._id) .first() .find() if (queriedPerson != null) { queriedPerson.name = person.name queriedPerson.phoneNumber = person.phoneNumber } else { Log.d("MongoRepository", "Queried Person does not exist.") } } } }
HomeViewModel
class HomeViewModel : ViewModel() { var objectId = mutableStateOf("") var data = mutableStateOf(emptyList<Person>()) // Profile + PickGender screen var name = mutableStateOf("") var phoneNumber = mutableStateOf(0) init { viewModelScope.launch { MongoDB.getData().collect { data.value = it } } } fun updateObjectId(id: String) { this.objectId.value = id } fun updateName(name: String) { this.name.value = name } fun updatePhoneNumber(phoneNumber: Int) { this.phoneNumber.value = phoneNumber } // The phoneNumber and Age will be set in string since I don't know how to set it in Int form fun insertPerson() { viewModelScope.launch(Dispatchers.IO) { if ( name.value.isNotEmpty() && phoneNumber.value.toString().isNotEmpty() ) { MongoDB.insertPerson(person = Person().apply { name = this@HomeViewModel.name.value phoneNumber = this@HomeViewModel.phoneNumber.value }) } } } fun updatePerson() { viewModelScope.launch(Dispatchers.IO) { if (objectId.value.isNotEmpty()) { MongoDB.updatePerson(person = Person().apply { _id = ObjectId(hexString = this@HomeViewModel.objectId.value) name = this@HomeViewModel.name.value phoneNumber = this@HomeViewModel.phoneNumber.value }) } } } }
实现方案
1. 导航前异步暂存数据
修改确认按钮的点击逻辑,先将输入数据同步到ViewModel暂存,再执行导航:
onClick = { if (name.isNotEmpty() && phoneNumber.isNotEmpty()) { // 暂存姓名 homeViewModel.updateName(name) // 处理手机号类型转换(带格式校验) runCatching { homeViewModel.updatePhoneNumber(phoneNumber.toInt()) }.onFailure { Toast.makeText(context, "请输入有效的数字电话号码", Toast.LENGTH_SHORT).show() return@onClick } // 隐藏键盘并导航 keyboardController?.hide() navController.navigate(AuthScreen.GenderProfile.route) } else { Toast.makeText(context, "请填写所有字段!", Toast.LENGTH_SHORT).show() } }
2. 主屏幕触发MongoDB写入
在主屏幕Composable中,用LaunchedEffect监听页面进入事件,触发数据写入操作:
@Composable fun HelloScreen(homeViewModel: HomeViewModel = viewModel()) { // 页面首次进入时执行数据写入 LaunchedEffect(Unit) { homeViewModel.insertPerson() } Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = "hello", fontSize = 24.sp) } }
3. 修复现有代码核心问题
(1)手机号类型适配
手机号用Int存储存在长度溢出风险(11位手机号超过Int最大值),建议将Person类的phoneNumber改为String类型:
// 修改Person类 var phoneNumber: String = "" // 同步修改ViewModel和输入逻辑 class HomeViewModel : ViewModel() { var phoneNumberStr = mutableStateOf("") fun updatePhoneNumberStr(str: String) { this.phoneNumberStr.value = str } fun insertPerson() { viewModelScope.launch(Dispatchers.IO) { if (name.value.isNotEmpty() && phoneNumberStr.value.isNotEmpty()) { MongoDB.insertPerson(person = Person().apply { name = this@HomeViewModel.name.value phoneNumber = this@HomeViewModel.phoneNumberStr.value owner_id = MongoDB.user?.id ?: "" }) } } } }
(2)Realm初始化优化
避免在init块中依赖可能为空的currentUser,改为延迟初始化:
object MongoDB : MongoRepository { private val app = App.create(APP_ID) private var user: User? = app.currentUser private var realm: Realm? = null override fun configureTheRealm() { user = app.currentUser if (user != null && realm == null) { val config = SyncConfiguration.Builder( user!!, setOf(Person::class) ) .initialSubscriptions { sub -> add(query = sub.query<Person>("owner_id == $0", user!!.id)) } .log(LogLevel.ALL) .build() realm = Realm.open(config) } } override suspend fun insertPerson(person: Person) { val currentRealm = realm ?: return val currentUser = user ?: return currentRealm.write { try { copyToRealm(person.apply { owner_id = currentUser.id }) } catch (e: Exception) { Log.d("MongoRepository", e.message.toString()) } } } }
(3)ViewModel数据流收集优化
用repeatOnLifecycle避免后台持续收集流导致的内存泄漏:
init { viewModelScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { MongoDB.getData().collect { data.value = it } } } }
优化反馈
- 手机号建议用String存储,避免长度溢出和格式限制问题
- 输入框可添加手机号格式校验(如长度、开头数字规则)
- 所有ViewModel的MutableState建议添加
private set,限制外部直接修改,保证数据一致性 - Realm操作需增加网络异常处理(如同步失败时的重试逻辑)
- 可添加加载状态提示(如写入DB时显示加载动画)
内容的提问来源于stack exchange,提问作者Josef M
相关产品推荐
相关产品推荐

