Search code examples
androidcoding-styleviewmodelandroid-jetpack-compose

How can I manage a lots of States in jetpack compose


I am building an app has has a lots of input fields so with those input fields I have to manage a lots of states in my viewModel.

Due to this my viewModel is a total mess. Can anyone guide me to handle these states efficiently with clean code.

The state management pattern that I am using is mentioned in android official documentation.

My viewModels looks like this-

@HiltViewModel
class CardsViewModel @Inject constructor(
    private val repository: CardsRoomRepository
): ViewModel() {


    private val _results = MutableStateFlow<List<CardsItems>>(emptyList())
    val results: StateFlow<List<CardsItems>> = _results

    private val _resultsForFavorites = MutableStateFlow<List<CardsItems>>(emptyList())
    val resultsForFavorites: StateFlow<List<CardsItems>> = _resultsForFavorites

    private val _resultsForSearch = MutableStateFlow<List<CardsItems>>(emptyList())
    val resultsForSearch: StateFlow<List<CardsItems>> = _resultsForSearch

    private val _switch = mutableStateOf(false)
    var switch: State<Boolean> = _switch
    fun setSwitch(newText: Boolean) {
        _switch.value = newText
    }

    private val _searchQuery = mutableStateOf("")
    val searchQuery: State<String> = _searchQuery
    fun setSearchQuery(newText: String){
        _searchQuery.value = newText
    }

    init {

        getAllLoginsItems()
        getAllFavoriteCardsItems()

    }




    private val _title = mutableStateOf("")
    var title: State<String> = _title
    fun setTitle(newText: String) {
        _title.value = newText
    }

    private val _category = mutableStateOf(0)
    val category: State<Int> = _category
    fun setCategory(newText: Int) {
        _category.value = newText
    }

    private val _cardNumber = mutableStateOf("")
    val cardNumber: State<String> = _cardNumber
    fun setCardNumber(newText: String) {
        _cardNumber.value = newText
    }

    private val _cardHolderName = mutableStateOf("")
    var cardHolderName: State<String> = _cardHolderName
    fun setCardHolderName(newText: String) {
        _cardHolderName.value = newText
    }



    private val _pinNumber = mutableStateOf("")
    var pinNumber: State<String> = _pinNumber
    fun setPinNumber(newText: String) {
        _pinNumber.value = newText
    }

    private val _cvvNumber = mutableStateOf("")
    var cvvNumber: State<String> = _cvvNumber
    fun setCVVNumber(newText: String) {
        _cvvNumber.value = newText
    }

    private val _issueDate = mutableStateOf("")
    var issueDate: State<String> = _issueDate
    fun setIssueDate(newText: String) {
        _issueDate.value = newText
    }

    private val _expiryDate = mutableStateOf("")
    var expiryDate: State<String> = _expiryDate
    fun setExpiryDate(newText: String) {
        _expiryDate.value = newText
    }

    ....


}

And

@HiltViewModel
class OthersViewModel @Inject constructor(
    private val repository: OthersRoomRepository
) : ViewModel() {

    private val _results = MutableStateFlow<List<OthersItems>>(emptyList())
    val results: StateFlow<List<OthersItems>> = _results

    private val _resultsForFavorites = MutableStateFlow<List<OthersItems>>(emptyList())
    val resultsForFavorites: StateFlow<List<OthersItems>> = _resultsForFavorites

    private val _resultsForSearch = MutableStateFlow<List<OthersItems>>(emptyList())
    val resultsForSearch: StateFlow<List<OthersItems>> = _resultsForSearch

    private val _switch = mutableStateOf(false)
    var switch: State<Boolean> = _switch
    fun setSwitch(newText: Boolean) {
        _switch.value = newText
    }

    private val _searchQuery = mutableStateOf("")
    val searchQuery: State<String> = _searchQuery
    fun setSearchQuery(newText: String) {
        _searchQuery.value = newText
    }

    init {

        getAllOthersItems()
        getAllFavoriteOthersItems()

    }


    private val _title = mutableStateOf("")
    var title: State<String> = _title
    fun setTitle(newText: String) {
        _title.value = newText
    }

    private val _category = mutableStateOf(0)
    val category: State<Int> = _category
    fun setCategory(newText: Int) {
        _category.value = newText
    }

    private val _userName = mutableStateOf("")
    val userName: State<String> = _userName
    fun setUserName(newText: String) {
        _userName.value = newText
    }

    private val _password = mutableStateOf("")
    val password: State<String> = _password
    fun setPassword(newText: String) {
        _password.value = newText
    }

    private val _description = mutableStateOf("")
    val description: State<String> = _description
    fun setDescription(newText: String) {
        _description.value = newText
    }

    private val _macAddress = mutableStateOf("")
    val macAddress: State<String> = _macAddress
    fun setMacAddress(newText: String) {
        _macAddress.value = newText
    }


    ....

}

Solution

  • I don't think there's any point in creating a setter for each property, in your example. It can be useful when you have some logic which not just updating the value.

    You can move your states into separate classes. As long as you have them as state objects, updating them will trigger recomposition.

    Let's say you have a CardView which draw your card with number, name, etc. If you don't have it in a separate view, that's an other good practice to split your composables into small views. In addition to the convenience of splitting the data into chunks, it will help with reconfiguration and is much easier to read and develop.

    So group all data that's being used by this view into CardData:

    class CardData {
        val cardNumber = mutableStateOf("")
        val cardHolderName = mutableStateOf("")
        val pinNumber = mutableStateOf("")
        val cvvNumber = mutableStateOf("")
        val issueDate = mutableStateOf("")
        val expiryDate = mutableStateOf("")
    }
    

    Store it in your view model:

    @HiltViewModel
    class CardsViewModel @Inject constructor(
        private val repository: CardsRoomRepository
    ): ViewModel() {
        ...
        val cardData = CardData()
    }
    

    And pass to your view:

    @Composable
    fun Screen(viewModel: CardsViewModel() = viewModel()) {
        ...
        CardView(viewModel.cardData)
    }
    

    And if you're using these setters to pass to text fields, you can unwrap them like this:

    @Composable
    fun CardView(cardData: CardData) {
        val (cardHolderName, cardHolderNameSetter) = cardData.cardHolderName
        TextField(cardHolderName, cardHolderNameSetter)
    }