Jetpack Compose ViewModel Update UI Using Flow

LiveData is commonly used for reflecting recent changes in data sources in XML-based applications. Also, it is a widely accepted solution for that.

But, when you move on to New Declarative UI Jetpack Compose. You can use LiveData for reflecting changes, but, Flow Is more efficient and reliable to use with Jetpack Commpose.

First We will talk about the architecture based on which we will make our app. Today, Mobile app developers mostly use the Clean Architecture MVVM, and MVI as their preferred app architecture.

  1. Update Data Using Flow

Note : You can create a new project in Latest Android Studio or Download Source From Github Repo.

We are creating a sample Composableswith Two Buttons And One Text. Here is a Row with Three Child One Button For Increment The Counter Another For Decrease The Counter And Last One For The Showing Counter A Text

  • horizontalArrangment : this will ensure all the components have even spacing between them.
  • verticalAlignment : show content in the center of the current screen.
  • onClick{} : we have used the lamdas rather than passing the ViewModel instance to the Composable so that we. can avoid unnecessary recompositions.

State Hoisting Best Practices : https://developer.android.com/jetpack/compose/state-hoisting

@Composable
fun FlowExample(counter: Int, onAdd: () -> Unit, onRemove: () -> Unit) {
    Row(
        modifier = Modifier
            .padding(horizontal = 10.dp)
            .fillMaxSize(),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.SpaceAround
    ) {
        Button(onClick = { onAdd() }) {
            Text(text = "Add")
        }
        Box(
            modifier = Modifier
                .padding(horizontal = 10.dp)
                .fillMaxWidth()
                .weight(1f)
                .clip(shape = RoundedCornerShape(12.dp))
                .background(color = Color.Black),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "Counter Value :: $counter ",
                modifier = Modifier.padding(vertical = 8.dp),
                color = Color.White
            )
        }
        Button(onClick = { onRemove() }) {
            Text(text = "Remove")
        }
    }
}

After creating the composable Comopsables. let’s code the backbone of the app means Business logic.

Preview

MainViewModel

import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow


class MainViewModel : ViewModel(){
    //Create  a flow for counter
    private val _counter = MutableStateFlow(0)

    //Expose immutable flow using asStateFlow()
    val counter = _counter.asStateFlow()

    init {
        changeCounterValue(0)
    }

    private fun changeCounterValue(cnt : Int){
        _counter.value = cnt
    }

    fun incrementCounter (){
        _counter.value = _counter.value + 1
    }

    fun decrementCounter(){
        _counter.value = _counter.value - 1
    }
}
  • Create a simple ViewModel by extending the ViewModel() and create MutableStateFlow(0) with an initial value of zero and keep it private so other sources can not edit them only ViewModel can edit it.
  • So we have to create another variable which will be immutable means Read-Only. which we will observe as a state in the Composables
  • There is two functions for incrementing and decrease of the counter which is exposed to the Compsoables.

How to use it

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.md.jetpackcomposeupdateuiusingviewmodel.ui.theme.JetpackComposeUpdateUIUsingViewModelTheme
import androidx.compose.ui.Alignment
import androidx.lifecycle.compose.collectAsStateWithLifecycle

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

        val mainViewModel: MainViewModel by viewModels()

        setContent {
            JetpackComposeUpdateUIUsingViewModelTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    val counter by mainViewModel.counter.collectAsStateWithLifecycle()

                    FlowExample(counter = counter,
                        onAdd = {
                            mainViewModel.incrementCounter()
                        },
                        onRemove = {
                            mainViewModel.decrementCounter()
                        })
                }
            }
        }
    }
}
  • After creating the design and ViewModel we need to observe the changes so that we can update the UI as the value changes and all this thing will be handled by MainViewModel
  • collectAsStateWithLifecycle used to collect the latest value from the StateFlow in a lifecycle-aware manner. so whenever it is not required it will be disposed

Don’t forget to add the following dependency to the gradle for using the collectAsStateWithLifecycle

implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.2")

Final App Preview

🍴Check out the complete code on my GitHub Gist. ✍️ Hope this project really helps you.

Hope you enjoy coding Jetpack Compose 😁. Don’t forget to share 📨 and clap 👏.

Any Suggestions are welcome. If you need any help or have questions for Code Contact Me. You can follow me on LinkedInStackOverflow and Twitter For More Updates 🔔

Happy Compose !! 🚀

Leave a Comment