You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 10:36:23