Home > Mobile >  How to split viewmodel or build master viewmodel with small viewmodels?
How to split viewmodel or build master viewmodel with small viewmodels?

Time:10-20

I have one pretty big complicated ViewModel and I want to split it or build it with few smaller ViewModels.

Below I want to show how I make my ViewModels in general (please do not laugh, this is my first Android ViewModel). I'm not using DataBinding, just ViewBinding.

class AssignUserTagToInventoryItemViewModel() : ViewModel() {

    private val UserTag = "MyApp"   this.javaClass.simpleName

    init {
        Log.d(UserTag, "Class init called")
        loadInventoryItems()
        loadRandomUserTags() // todo: replace with real implementation
    }

    private var allItems = ArrayList<InventoryItemDto?>()

    //<editor-fold desc="FilterByName">
    private val _filterByName = MutableLiveData<String>("")
    val filterByName: LiveData<String> get() = _filterByName
    fun setFilterByName(t : String) { _filterByName.value = t; applyFilters();}
    //</editor-fold>

    //<editor-fold desc="FilterByAssignedToMe">
    private val _filterByAssignedToMe = MutableLiveData<Boolean>(false)
    val filterByAssignedToMe: LiveData<Boolean> get() = _filterByAssignedToMe
    fun setFilterByAssignedToMe(t : Boolean) { _filterByAssignedToMe.value = t; applyFilters(); }
    //</editor-fold>

    //<editor-fold desc="SelectedInventoryItem">
    private val _selectedInventoryItem = MutableLiveData<InventoryItemDto?>(null)
    fun getSelectedInventoryItem() : LiveData<InventoryItemDto?> = _selectedInventoryItem
    fun setSelectedInventoryItem(itemDto: InventoryItemDto?) {
        _selectedInventoryItem.value = itemDto
        selectedItemOrUserTagChanged()
    }
    //</editor-fold>

    // <editor-fold desc="FilteredItems">
    val _displayedItems = MutableLiveData<ArrayList<InventoryItemDto?>>(ArrayList())
    val displayedItems: LiveData<ArrayList<InventoryItemDto?>> get() = _displayedItems
    // </editor-fold>

    // <editor-fold desc="ItemsListError">
    val _itemsListError = MutableLiveData<String>("")
    val itemsListError :LiveData<String> get() = _itemsListError
    fun setItemsListError(s : String) { _itemsListError.value = s }
    // </editor-fold>

    //<editor-fold desc="UserTag list">
    val _UserTags = MutableLiveData<ArrayList<UserTag>>(ArrayList())
    val UserTags : LiveData<ArrayList<UserTag>> get() = _UserTags
    fun setUserTags(a : ArrayList<UserTag>) { _UserTags.value = a }
    //</editor-fold>

    //<editor-fold desc="SelectedUserTagItem">
    private val _selectedUserTag = MutableLiveData<UserTag?>(null)
    fun getSelectedUserTag() : LiveData<UserTag?> = _selectedUserTag
    fun setSelectedUserTag(UserTag : UserTag?) {
        _selectedUserTag.value = UserTag
        selectedItemOrUserTagChanged()
    }
    //</editor-fold>

    //<editor-fold desc="CanSubmit">
    private val _canSubmit = MutableLiveData<Boolean>(false)
    val canSubmit: LiveData<Boolean> get() = _canSubmit
    //</editor-fold>

    private fun selectedItemOrUserTagChanged() {
        _canSubmit.value = true
    }

    private fun loadInventoryItems(){
        Log.d(UserTag, "Loading inventory items...")
        viewModelScope.launch {
            try {
                val apiResponse = ApiResponse(ApiAdapter.apiClient.findAllInventoryItems())
                if (apiResponse.code == 200 && apiResponse.body != null) {
                    allItems = apiResponse.body
                    applyFilters()
                    Log.d(UserTag, "Loading inventory items done.")
                }
                else {
                    setItemsListError(apiResponse.code.toString())
                    Log.d(UserTag, "Loading inventory items error.")
                }
            } catch (t : Throwable) {
                setItemsListError(t.message.toString())
            }
        }
    }

    private fun applyFilters(){
        Log.d(UserTag, "ViewModel apply filters called. Current name filter: ${filterByName.value}")

        val tempResults = ArrayList<InventoryItemDto?>()
        val nameFilterLowercase = filterByName.value.toString().lowercase()

        if (!filterByName.value.isNullOrEmpty()) {
            for (item in allItems) {
                val itemNameLowercase = item?.name?.lowercase()?:""
                if (itemNameLowercase.contains(nameFilterLowercase))
                    tempResults.add(item)
            }
            _displayedItems.value = tempResults
        } else {
            _displayedItems.value = allItems
        }
    }

    private fun loadRandomUserTags(){
        val temp = ArrayList<UserTag>()

        for (i in 1..50){
            val epc = getRandomHexString(24).uppercase()

            val UserTag = UserTag(epc,  0, "0")
            temp.add(UserTag)
        }

        viewModelScope.launch {
            delay(100)

            _UserTags.value = temp
        }
    }

    private fun getRandomHexString(numchars: Int): String {
        val r = Random()
        val sb = StringBuffer()
        while (sb.length < numchars) {
            sb.append(Integer.toHexString(r.nextInt()))
        }
        return sb.toString().substring(0, numchars)
    }
}

CodePudding user response:

Simply create multiple view models according to the task they are performing. There are several problems here :

  1. Your ViewModel name is too long
  2. You can create an object of the getRandomHexString and this way you can use it inside any other classes or ViewModels you may need in future. It also saves space inside ViewModel.
  3. Learn about the clean architecture and follow its practices. Here, you can create a separate view model or helper class for filtering your results. If you create another view model, you can simply retrieve results from your current view model to the activity and call filter view model inside your activity. This way you can separate code blocks according to the role they play or the function they perform.
  • Related