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

两个ViewModel复用调用Repository函数的最佳实践咨询

最佳实践方案

针对你遇到的ViewModel逻辑复用问题,以下是几种符合架构原则的解决方案,优先推荐使用UseCase模式,兼顾解耦性与复用性:


1. 使用UseCase(推荐)

UseCase是Clean Architecture中专门封装业务逻辑的层,能将数据获取、转换逻辑从ViewModel中剥离,做到一次实现多ViewModel复用,同时遵循Repository仅负责数据库操作的原则。

实现步骤:

1.1 封装UseCase类

把getPartsList()的数据转换逻辑、协程处理封装到独立的UseCase中,依赖Repository完成数据库调用:

// 封装获取零件列表的业务逻辑
class GetPartsListUseCase(
    private val partsRepository: PartsScreenRepository,
    private val imageUrlProvider: ImageUrlProvider // 单独封装图片URL获取逻辑,解耦依赖
) {
    // 重载invoke运算符,简化调用
    suspend operator fun invoke(): List<Part> {
        // 调用Repository获取原始数据
        val querySnapshot = partsRepository.getPartsList()
        // 转换QuerySnapshot为Part列表,处理空值安全
        return querySnapshot.documents.mapNotNull { document ->
            val partNumber = document.getString("part_number") ?: return@mapNotNull null
            val partDesc = document.getString("part_desc") ?: return@mapNotNull null
            val partImage = imageUrlProvider.getImageUrl(partNumber)
            Part(partNumber, partDesc, partImage)
        }
    }
}

// 单独封装图片URL获取逻辑,避免硬依赖Firebase
class ImageUrlProvider(private val storage: FirebaseStorage) {
    fun getImageUrl(partNumber: String): String {
        return storage.reference.child("parts/$partNumber.jpg").downloadUrl.toString()
    }
}

1.2 ViewModel中依赖UseCase

两个ViewModel通过依赖注入获取同一个UseCase实例,直接调用逻辑即可:

class FirstViewModel(
    private val getPartsListUseCase: GetPartsListUseCase
) : ViewModel() {
    fun getOrderSummary(order: List<OrderReportData>): List<OrderPart> {
        // 使用viewModelScope处理协程,避免runBlocking阻塞主线程
        val partsList = viewModelScope.async {
            getPartsListUseCase()
        }.await()

        // 优化后的订单汇总逻辑,处理空安全与异常
        return partsList.mapNotNull { part ->
            val orderCount = order.filter { it.orderPart.number == part.number }
            val orderPartQuantityList = try {
                orderCount.map { it.orderPart.qty.toDouble() }
            } catch (e: NumberFormatException) {
                Log.e("mytags", "Invalid number format", e)
                emptyList()
            }
            val partQuantityDouble = orderPartQuantityList.sum()
            val partQuantity = if (partQuantityDouble.rem(1) == 0.0) {
                partQuantityDouble.toInt().toDouble()
            } else {
                partQuantityDouble.toBigDecimal().setScale(1, RoundingMode.FLOOR).toDouble()
            }
            val orderPart = OrderPart(part.number, part.desc, partQuantity.toString())
            if (orderPart.qty.toFloat() > 0) orderPart else null
        }
    }
}

// 第二个ViewModel复用相同逻辑
class SecondViewModel(
    private val getPartsListUseCase: GetPartsListUseCase
) : ViewModel() {
    // 同样可以调用getPartsListUseCase()或复用订单汇总逻辑
}

2. 基类ViewModel(适合高度相关的ViewModel)

如果两个ViewModel的业务逻辑高度重叠,可以创建一个基类ViewModel,将复用逻辑放入基类,子类继承后直接使用:

abstract class BasePartsViewModel(
    protected val partsRepository: PartsScreenRepository,
    protected val imageUrlProvider: ImageUrlProvider
) : ViewModel() {
    protected suspend fun getPartsList(): List<Part> {
        val querySnapshot = partsRepository.getPartsList()
        return querySnapshot.documents.mapNotNull { document ->
            val partNumber = document.getString("part_number") ?: return@mapNotNull null
            val partDesc = document.getString("part_desc") ?: return@mapNotNull null
            val partImage = imageUrlProvider.getImageUrl(partNumber)
            Part(partNumber, partDesc, partImage)
        }
    }

    protected fun getOrderSummary(order: List<OrderReportData>, partsList: List<Part>): List<OrderPart> {
        // 订单汇总逻辑
        return partsList.mapNotNull { part ->
            val orderCount = order.filter { it.orderPart.number == part.number }
            val orderPartQuantityList = try {
                orderCount.map { it.orderPart.qty.toDouble() }
            } catch (e: NumberFormatException) {
                Log.e("mytags", "Invalid number format", e)
                emptyList()
            }
            val partQuantityDouble = orderPartQuantityList.sum()
            val partQuantity = if (partQuantityDouble.rem(1) == 0.0) {
                partQuantityDouble.toInt().toDouble()
            } else {
                partQuantityDouble.toBigDecimal().setScale(1, RoundingMode.FLOOR).toDouble()
            }
            val orderPart = OrderPart(part.number, part.desc, partQuantity.toString())
            if (orderPart.qty.toFloat() > 0) orderPart else null
        }
    }
}

// 子类继承基类
class FirstViewModel(
    partsRepository: PartsScreenRepository,
    imageUrlProvider: ImageUrlProvider
) : BasePartsViewModel(partsRepository, imageUrlProvider) {
    fun getOrderSummary(order: List<OrderReportData>): List<OrderPart> {
        val partsList = viewModelScope.async { getPartsList() }.await()
        return super.getOrderSummary(order, partsList)
    }
}

关键优化点说明

  • 移除原代码中的runBlocking:ViewModel中必须使用viewModelScope管理协程,避免阻塞主线程导致ANR。
  • 空安全处理:用mapNotNull替代原代码中的可空列表,避免空指针异常。
  • 职责分离:Repository仅负责数据库调用,数据转换、业务逻辑交给UseCase或基类,符合单一职责原则。

内容的提问来源于stack exchange,提问作者Andrew Payne

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 18:47:57