两个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
相关产品推荐
相关产品推荐

