Kotlin:将ViewModel方法包装在对象中

时间:2020-06-24 17:56:25

标签: android kotlin mvvm viewmodel android-livedata

我遵循的是MVVM设计模式,而我的某些视图模型最终采用了很多方法将活动数据暴露给活动/片段。

我想知道是否可以将这些吸气剂方法“分组”,然后我想到了这个……

  class MyViewModel(private val repository: MyRepository) : ViewModel() {

    /** RAW DATA: data received directly from the api **/
    private val apiData1 = repository.getApiData1()
    private val apiData2 = repository.getApiData2()

    inner class ApiData {
        // Expose only api data 1
        fun getApiData1() = apiData1
    }

    /** UI DATA: data conveniently formatted for the UI **/
    private var uiDataA = UiFactory.buildUiDataA(apiData1)
    private var uiDataB = UiFactory.buildUiDataB(apiData2)
    private var uiDataC = UiFactory.buildUiDataC(apiData1, apiData2)

    inner class UiData {
        // Expose all ui data
        fun getUiDataA() = uiDataA
        fun getUiDataB() = uiDataB
        fun getUiDataC() = uiDataC
    }
}

那样,如果我想访问一个方法,就需要实例化其特定的包装器。

对于原始数据:viewModel.RawData().getApiData1()

对于ui数据:viewModel.UiData().getUiDataA()

class MyActivity {

    val vm: MyViewModel = ... // Load view model:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        ...
        vm.UiData().getUiDataA().observe(...)
    }
}

您能告诉我这是否是包装方法的好方法吗?这是好/坏做法吗?替代品?

谢谢

1 个答案:

答案 0 :(得分:0)

有我们的MVI(或您的情况下的MVVMI)模式可以帮助您对V​​M的公开数据进行分组。因此,您将拥有一个包含所有行值的State对象,而不是使用多个LiveData。 Kotlin的密封班可以为您提供帮助:

sealed class UiState {
    data class Data(
        val uiDataA: Any,
        val uiDataB: Any,
        val uiDataC: Any
    ) : UiState()
    object Loading : UiState()
    data class Error(val cause: Throwable) : UiState()
}

在您的代码中,您将从VM init上的存储库中获取数据。

/ **原始数据:直接从api接收的数据** /
私有val apiData1 = repository.getApiData1()

我建议您避免这种情况,并且仅根据视图需求或在后台(例如,使用协程)仅延迟获取所有数据

class MyViewModel(
    private val uiService: UiFactory,
    private val getRawDataUseCase: GetRawApiDataUseCase
) : ViewModel() {

    /** RAW DATA: data received directly from the api **/
    fun fetchApiData() = getRawDataUseCase() // TODO: make it async
    
    /** UI DATA: data conveniently formatted for the UI **/
    fun fetchUiData() = liveData<UiState> {
        emit(UiState.Loading)
        try {
            emit(UiState.Data(
                uiDataA = uiService.buildUiDataA(),
                uiDataB = uiService.buildUiDataB(),
                uiDataC = uiService.buildUiDataC()
            ))
        } catch (t:Throwable) {
            emit(UiState.Error(t))
        }
    }
}

UiFactory的干净架构中,功能基本上是用例。您可以将类拆分为一堆用例类,也可以保持不变。在干净的体系结构中,您的UiFactory将是ServiceClean architecture diagram

最好不要从VM(表示层)访问存储库(数据层)。因此您的域层将连接它们,并将包含2个类:

class UiFactory(private val repository: MyRepository) { //better name it UiService
    fun buildUiDataA(): Any {
        val apiData1 = repository.getApiData1()
        // conveniently format your data apiData1
    }

    fun buildUiDataB(): Any {
        val apiData2 = repository.getApiData1()
        // conveniently format your data apiData1
    }

    fun buildUiDataC(): Any {
        // you can store your apiData1 and apiData2 in this class to not duplicate
        // repository calls
    }
}

class GetRawApiDataUseCase(private val repository: MyRepository) {
    operator fun invoke() = repository.getApiData1()
}

然后您的活动将处理以下状态:

class MyActivity {

    val vm: MyViewModel = ... // Load view model:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        ...
        vm.fetchUiData().observe {
            when(it) {
                is UiState.Data -> {
                    display(it.uiDataA)
                    display(it.uiDataB)
                    display(it.uiDataC)
                    //display data 
                }
                is UiState.Error -> displayError(it.cause)
                UiState.Loading -> showLoading()
            }
        }
    }
}

顺便说一句,it's not the best practice to store LiveData in your repository(您的代码尚不清楚,但我假设您将数据存储在此处)。

您的方法可能是可行的解决方案,但是某些平台的常规做法可以为您节省大量开发时间。而且,使用流行的设计方法可以简化团队中新成员的加入:)