Clean Architecture与MVVM中,如何用Mapper传递Domain层数据至Fragment?
问题:Clean Architecture + MVVM下跨Fragment传递Domain层数据
我正在学习Clean Architecture与MVVM架构,遇到了一个问题:
现有两个Fragment(A和B),均采用MVVM模式,各自拥有ViewModel与Repository,Repository归属于Data层。
B的Repository不做本地/远程数据存储,仅负责创建和删除Domain层的SetInfo模型数据。我想把生成的数据传递给Fragment A,但Domain层是纯Kotlin实现,没法用Parcelable接口。
看到同类问题提到要在Presentation层创建Model类并使用Data Mapper,但不清楚具体怎么实现,求帮助。
现有代码结构
Domain层
data class SetInfo( val set: Int, var weight: String = "", var reps: String = "", var unit: WorkoutUnit = WorkoutUnit.kg, )
Data层 - BRepository
class BRepository() { private var setInfoList = ArrayList<SetInfo>() fun addSet() { setInfoList.let { list -> val item = SetInfo(set = setInfoList.size + 1) list.add(item) } } fun deleteSet() { if(setInfoList.size != 0) { setInfoList.let { list -> list.removeLast() } } return } }
Data层 - SetInfoEntity(数据库实体)
@Entity( foreignKeys = [ ForeignKey( entity = Workout::class, parentColumns = arrayOf("workoutId"), childColumns = arrayOf("parentWorkoutId"), onDelete = ForeignKey.CASCADE ) ] ) data class SetInfoEntity( @PrimaryKey(autoGenerate = true) val id: Long = 0, val set: Int, var weight: String = "", var reps: String = "", var unit: WorkoutUnit = WorkoutUnit.kg, val parentWorkoutId: Long = 0 )
更新尝试的代码
Presentation层
B Fragment
// 使用Navigation Component的Safe Args从Fragment B传递数据到Fragment A class BFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = FragmentBBinding.inflate(inflater, container, false) save.setOnClickListener { val uiModel : SetInfoUiModel = vm.getData() // vm是ViewModel实例 BFragmentDirections.actionBFragmenttoAFragment(uiModel) } return binding.root } }
A Fragment
// 接收从B传来的数据并传递给A的Repo(遵循MVVM模式存储到DB) class AFragment : Fragment() { private val args: AFragmentArgs by navArgs() private lateinit var vm: AViewModel override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = FragmentABinding.inflate(inflater, container, false) val setInfoUiModel = args.uiModel vm = AViewModel(setInfoUiModel) // 通过ViewModel传递给A的Repo return binding.root } }
BViewModel
class BViewModel() : ViewModel() { private val repository: BRepository = BRepository() fun getData() = repository.getData() }
AViewModel
class AViewModel() : ViewModel() { private val repository: ARepository = ARepository() fun save(data: SetInfoUiModel) { repository.save(data) } }
Domain层 - SetInfo
data class SetInfo( ... // 原有属性 )
Data层 - SetInfoUiModel
@Parcelize data class SetInfoUiModel( ... // 与SetInfo对应的属性 ): Parcelable
B Repository
class BRepository() { private var setInfo = SetInfo() ... // 其他代码 // 扩展函数:将Domain层的SetInfo转换为Presentation层的SetInfoUiModel fun SetInfo.toUiModel() = SetInfoUiModel(...) fun getData() = setInfo.toUiModel(...) }
A Repository
class ARepository() { private lateinit var setInfoUiModel : SetInfoUiModel // 从A的ViewModel接收B传来的数据,存储到DB fun save(data : SetInfoUiModel) { setInfo = data ... // 数据库存储逻辑 } }
完整解决方案说明
1. 明确Mapper的核心作用
Mapper用来在不同层的模型间做转换,保证各层独立性:
- Domain → Presentation:把纯Kotlin的
SetInfo转成实现Parcelable的SetInfoUiModel,用于Fragment间传递 - Presentation → Domain:把
SetInfoUiModel转回SetInfo,还原业务模型 - Domain → Data:把
SetInfo转成数据库实体SetInfoEntity,适配存储
2. 修正代码中的关键问题
(1)完善BRepository的转换逻辑
class BRepository() { private var setInfoList = ArrayList<SetInfo>() // 假设你要传递最后一组编辑完成的数据 fun getLatestSetInfo(): SetInfo? { return setInfoList.lastOrNull() } // 扩展函数:Domain转Presentation模型 private fun SetInfo.toUiModel() = SetInfoUiModel( set = this.set, weight = this.weight, reps = this.reps, unit = this.unit ) fun getLatestSetInfoAsUiModel(): SetInfoUiModel? { return getLatestSetInfo()?.toUiModel() } // 原有addSet、deleteSet逻辑保留 }
(2)修正AViewModel的实例化方式
不能直接new ViewModel,要使用ViewModelProvider保证生命周期管理:
class AFragment : Fragment() { private val args: AFragmentArgs by navArgs() private lateinit var vm: AViewModel override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = FragmentABinding.inflate(inflater, container, false) // 正确实例化ViewModel vm = ViewModelProvider(this)[AViewModel::class.java] // 调用save方法传递数据 args.uiModel?.let { vm.save(it) } return binding.root } }
(3)完善ARepository的转换与存储逻辑
class ARepository() { // 假设你有数据库DAO实例 private lateinit var setInfoDao: SetInfoDao // 扩展函数:Presentation转Domain模型 private fun SetInfoUiModel.toDomainModel() = SetInfo( set = this.set, weight = this.weight, reps = this.reps, unit = this.unit ) // 扩展函数:Domain转数据库实体 private fun SetInfo.toEntity(parentWorkoutId: Long) = SetInfoEntity( set = this.set, weight = this.weight, reps = this.reps, unit = this.unit, parentWorkoutId = parentWorkoutId ) fun save(data: SetInfoUiModel, parentWorkoutId: Long) { val domainModel = data.toDomainModel() val entity = domainModel.toEntity(parentWorkoutId) // 执行数据库插入 setInfoDao.insert(entity) } }
3. Safe Args配置注意事项
在Navigation Graph中,给actionBFragmenttoAFragment配置参数时,类型要选择SetInfoUiModel,确保Safe Args生成的代码能正确处理序列化。
内容的提问来源于stack exchange,提问作者ybybyb
相关产品推荐
相关产品推荐

