如何在Kotlin Compose中通过数据类实现Composable代码复用?
Kotlin Compose 通用Composable实现,消除数据类差异导致的代码冗余
你有两个逻辑完全一致的Composable函数,仅接收的ArrayList数据类型不同,可通过以下步骤实现通用版本,彻底消除代码冗余:
步骤1:定义通用数据接口
由于HealthCategoryDetailItems和HealthTagDetailItems的核心字段完全一致,先提取一个接口,包含Composable中需要用到的所有属性:
interface HealthDetailItem { val articleTypeId: Int? val date: String? val image: String? val title: String? }
步骤2:修改数据类实现接口
让两个数据类都实现上述接口,统一属性的访问方式:
修改HealthCategoryDetailItems
data class HealthCategoryDetailItems( @SerializedName("article_type_id") override val articleTypeId: Int? = null, @SerializedName("date") override val date: String? = null, @SerializedName("image") override val image: String? = null, @SerializedName("id") val id: Int? = null, @SerializedName("title") override val title: String? = null ) : HealthDetailItem
修改HealthTagDetailItems
data class HealthTagDetailItems( @SerializedName("article_type_id") override val articleTypeId: Int? = null, @SerializedName("date") override val date: String? = null, @SerializedName("image") override val image: String? = null, @SerializedName("id") val id: Int? = null, @SerializedName("title") override val title: String? = null ) : HealthDetailItem
步骤3:编写泛型通用Composable
创建一个泛型Composable,约束泛型必须实现HealthDetailItem接口,这样就能兼容两种数据类型:
@Composable fun <T : HealthDetailItem> GenericHealthItemsCategory( items: ArrayList<T>, onClick: (Int?, String?) -> Unit // 抽离点击逻辑,让外部页面自行实现 ) { val boxQueryWidth = ScreenQuery( limits = arrayListOf( ScreenQueryLimit( max = 321, value = 135, ), ScreenQueryLimit( min = 321, max = 361, value = 160, ) ), direction = ScreenDirection.HORIZONTAL, defaultValue = 170 ).value().dp val itemsList = items.chunked(2) itemsList.forEach { listItem -> Row( modifier = Modifier .fillMaxWidth() .padding(bottom = 15.dp), horizontalArrangement = Arrangement.SpaceBetween ) { listItem.forEach { item -> HealthItem( width = boxQueryWidth, imageSize = boxQueryWidth, layoutPadding = 0.dp, itemId = item.articleTypeId, itemName = item.title ?: "", image = item.image ?: "", onClick = onClick, detailText = item.date, detailTextColor = MaterialTheme.colors.grayColor, shouldShowAdditionalImage = item.articleTypeId == 0, additionalImg = painterResource(id = R.drawable.ic_home_content_recipe), content = {} ) } } } }
步骤4:替换原有Composable使用通用版本
原有的两个Composable可以直接简化为调用通用版本,或者直接在页面中使用GenericHealthItemsCategory:
替换ItemsCategory
@Composable fun ItemsCategory( items: ArrayList<HealthCategoryDetailItems>, onClick: (Int?, String?) -> Unit ) { GenericHealthItemsCategory(items, onClick) }
替换TagDetailItem
@Composable fun TagDetailItem( items: ArrayList<HealthTagDetailItems>, onClick: (Int?, String?) -> Unit ) { GenericHealthItemsCategory(items, onClick) }
可选:ViewModel层抽象优化
如果两个ViewModel的逻辑也高度相似,可以进一步抽象基类ViewModel,减少重复代码:
// 定义Response基接口 interface BaseResponse { val isError: Int? } // 抽象基类ViewModel abstract class BaseHealthDetailViewModel<ResponseModel : BaseResponse> : ViewModel() { protected abstract val useCase: UseCase<*, Resource<ResponseModel>> private val _state = mutableStateOf(DetailScreenState()) val state: State<DetailScreenState> = _state data class DetailScreenState( val isLoading: Boolean = false, val error: Boolean = false, val response: ResponseModel? = null ) protected fun handleResult(result: Resource<ResponseModel>) { when (result) { is Resource.Success -> { _state.update { it.copy( isLoading = false, response = result.data, error = result.data?.isError == 1 ) } } is Resource.Loading -> _state.update { it.copy(isLoading = true) } is Resource.Error -> _state.update { it.copy(isLoading = false, error = true) } } } } // 让两个ResponseModel实现BaseResponse data class HealthCategoryDetailResponseModel( @SerializedName("is_error") override val isError: Int? = null, @SerializedName("data") var data: HealthCategoryDetailData? = HealthCategoryDetailData(), @SerializedName("description") var description: String? = null ) : BaseResponse data class HealthContentDetailTagResponseModel( @SerializedName("is_error") override val isError: Int? = null, @SerializedName("data") var data: Data? = Data(), @SerializedName("description") var description: String? = null ) : BaseResponse // 子类ViewModel实现 class HealthCategoryDetailViewModel( private val useCase: HealthCategoryDetailUseCase ) : BaseHealthDetailViewModel<HealthCategoryDetailResponseModel>() { override val useCase = useCase fun loadData(request: HealthCategoryDetailRequestModel) { viewModelScope.launch { useCase.invoke(request).collect { handleResult(it) } } } } class HealthTagDetailViewModel( private val useCase: TagDetailUseCase ) : BaseHealthDetailViewModel<HealthContentDetailTagResponseModel>() { override val useCase = useCase fun loadData(request: HealthContentDetailTagRequestModel) { viewModelScope.launch { useCase.invoke(request).collect { handleResult(it) } } } }
内容的提问来源于stack exchange,提问作者NewPartizal
相关产品推荐
相关产品推荐

