WearOS健身计数App:存储同步与MVVM架构实现咨询
问题解答:WearOS训练记录应用的存储、同步与MVVM架构整合
一、独立版/非独立版选择
你的判断完全合理,仅需同步整数计数的场景下,选独立版WearOS应用是最优解。独立版无需依赖手机即可运行,满足你“手表本地优先保存数据”的核心需求,同时也支持通过原生框架和手机交互同步。
二、存储与同步方案选型
1. 本地存储:Jetpack Datastore
用Jetpack Datastore(偏好型或Proto型)做手表本地存储非常合适,它比SharedPreferences更可靠,原生支持协程和Flow,能轻松处理按日期存储的动作计数这类结构化数据。
2. 跨设备同步方案对比
方案一:Wearable Data Layer API
- 适用场景:仅需手表与配对手机之间同步数据,无多设备/跨账号同步需求
- 实现思路:
- 手表端更新计数后,先写入Datastore,再通过Data Layer将数据推送到配对手机
- 手机端监听Data Layer的数据变化,同步到自身本地存储(如Room或Datastore)
- 优势:无需额外云服务,依托Android原生框架,配对设备间同步延迟低,蓝牙连接时无网络也能同步
- 劣势:仅支持配对设备间同步,设备未配对则无法同步
方案二:云存储(如Firebase Realtime Database、AWS Amplify)
- 适用场景:需要多设备(多块手表、不同手机)同步,或希望数据长期云端备份
- 实现思路:
- 手表端写入Datastore后,通过云SDK将数据上传至云端
- 手机端从云端拉取数据,同时监听云端变化实现实时同步
- 优势:支持多设备跨场景同步,数据有云端备份
- 劣势:需额外配置云服务,依赖网络连接,开发复杂度更高
更优方案推荐
如果核心需求只是手表与配对手机同步,优先选Wearable Data Layer API + Jetpack Datastore,开发成本低,符合Android原生体验;若未来有扩展多设备同步的需求,再引入云存储即可。
三、MVVM架构整合指导
核心分层逻辑
- Model层:
- 封装Datastore的读写操作,提供Flow数据流供上层观察
- 封装Data Layer或云存储的同步逻辑,作为数据同步的核心实现
- ViewModel层:
- 持有Model层实例,处理UI触发的业务逻辑(增减计数、触发同步)
- 将Model层的数据流转换为UI可观察的状态(如StateFlow)
- View层:
- 实现+/-按钮和计数显示的布局
- 监听ViewModel的状态变化更新UI,将用户操作传递给ViewModel
简化版代码示例
1. Model层:Datastore操作类
class WorkoutDataStore(private val context: Context) { private val dataStore = context.createDataStore(name = "workout_prefs") // 按日期存储训练数据,键格式为"workout_20250501_squat" suspend fun saveExerciseCount(date: String, exercise: String, count: Int) { dataStore.edit { prefs -> prefs["${date}_$exercise"] = count } } // 获取指定日期的所有训练数据 fun getDailyWorkout(date: String): Flow<Map<String, Int>> { return dataStore.data.map { prefs -> listOf("squat", "deadlift", "bench press").associateWith { exercise -> prefs["${date}_$exercise"] ?: 0 } } } }
2. ViewModel层:业务逻辑处理
class WorkoutViewModel(private val dataStore: WorkoutDataStore) : ViewModel() { private val currentDate = "20250501" // 实际可通过Date工具类获取当前日期 private val _dailyWorkout = MutableStateFlow<Map<String, Int>>(emptyMap()) val dailyWorkout: StateFlow<Map<String, Int>> = _dailyWorkout init { viewModelScope.launch { dataStore.getDailyWorkout(currentDate).collect { workoutData -> _dailyWorkout.value = workoutData } } } fun incrementExercise(exercise: String) { viewModelScope.launch { val currentCount = _dailyWorkout.value[exercise] ?: 0 dataStore.saveExerciseCount(currentDate, exercise, currentCount + 1) // 触发Data Layer同步逻辑,此处省略具体实现 } } fun decrementExercise(exercise: String) { viewModelScope.launch { val currentCount = _dailyWorkout.value[exercise] ?: 0 if (currentCount > 0) { dataStore.saveExerciseCount(currentDate, exercise, currentCount - 1) // 触发Data Layer同步逻辑,此处省略具体实现 } } } }
3. View层:WearOS Compose界面示例
@Composable fun WorkoutScreen(viewModel: WorkoutViewModel = viewModel()) { val workoutData by viewModel.dailyWorkout.collectAsState() Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { workoutData.forEach { (exercise, count) -> ExerciseCounter( exerciseName = exercise, count = count, onIncrement = { viewModel.incrementExercise(exercise) }, onDecrement = { viewModel.decrementExercise(exercise) } ) } } } @Composable fun ExerciseCounter(exerciseName: String, count: Int, onIncrement: () -> Unit, onDecrement: () -> Unit) { Row( modifier = Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceAround ) { Text(text = exerciseName, fontSize = 18.sp) Button(onClick = onDecrement) { Text("-") } Text(text = count.toString(), fontSize = 22.sp, modifier = Modifier.padding(0.dp, 0.dp, 0.dp, 4.dp)) Button(onClick = onIncrement) { Text("+") } } }
四、简化版项目扩展说明
基于上述代码搭建基础项目后,需补充两个核心部分:
- Data Layer同步逻辑:在ViewModel的
saveExerciseCount方法后,通过Wearable.getDataClient(context)将数据发送到手机;手机端注册DataClient.OnDataChangedListener接收数据,同步到自身存储 - 日期自动处理:用
SimpleDateFormat或LocalDate自动生成当日日期,替代硬编码的20250501
内容的提问来源于stack exchange,提问作者theproductivepickle
相关产品推荐
相关产品推荐

