Jetpack Compose Coroutine flow with LiveData/ViewModel in Android
Hello everyone, In this article, we are going to learn about the Jetpack Compose with LiveData and coroutines flow. In my last tutorial, we have learned about the Jetpack Compose introduction and about applying the app theme for light and dark mode in Jetpack Compose and how to use layouts, rows, columns, modifiers and Constraint layouts. I recommended reading all these articles to better understand the Jetpack Compose.
Let’s get started to understand how to use ViewModel and LiveData and coroutines flow in Jetpack Compose? First of all, we need to add jetpack compose dependency.
implementation "androidx.compose.runtime:runtime-livedata:1.0.5"
Suppose that we are doing a network call to fetch the list of movies from the server with the help of Retrofit. In this case, we need to write a Repository interface to handle the network call. To make a call in the background we need to write the coroutine scope to launch the thread in ViewModel. Let’s start with the first MyRepository class.
@DelicateCoroutinesApi class MyRepository @Inject constructor( private val myService: MyService ) { suspend fun loadAllMoviesFromAPI(page: Int) = flow { emit(myService.getPopularMovies(page)) } }
Here, I am converting this call into a coroutines flow and emitting the response. Now we need to use this coroutine flow in our view model to collect the response.
@HiltViewModel @DelicateCoroutinesApi class MyViewModel @Inject constructor(val myRepository: MyRepository) : ViewModel() { private val _movieLiveData = SingleLiveEvent<Resource<MoviesResponse>>() val movieLiveData: SingleLiveEvent<Resource<MoviesResponse>> get() = _movieLiveData init { loadAllMovies(1) } fun loadAllMovies(page: Int) { viewModelScope.launch { myRepository.loadAllMoviesFromAPI(page) .catch { e -> _movieLiveData.value = Resource.error(e.message.toString(), null) } .collect { it.results?.let { it1 -> myRepository.insertMovieList(it1) } _movieLiveData.value = Resource.success(it) } } } }
We used here LiveData to set/post the API response to view. In this case, we need to observe the state of live data.
val observeState = myViewModel.movieLiveData.observeAsState() when (observeState.value?.status) { Status.SUCCESS -> { observeState.value?.data?.let { it.results?.let { list -> InitView(modifier = modifier, list, itemClick) } } } Status.LOADING -> { } Status.ERROR -> { } }
If the API response is succeeded then the state will be success else Error needs to handle correspondingly. Let’s say we received the list of movie and we need to show in the list with help of Jetpack compose. here is complete source code.
@ExperimentalFoundationApi @Composable fun MovieListScreen(myViewModel: MyViewModel, itemClick: (MovieEntity) -> Unit = {}) { ConstraintLayout { val (body, progress) = createRefs() Scaffold( backgroundColor = MaterialTheme.colors.primarySurface, topBar = { AppBar() }, modifier = Modifier.constrainAs(body) { top.linkTo(parent.top) } ) { val modifier = Modifier.padding(it) val observeState = myViewModel.movieLiveData.observeAsState() when (observeState.value?.status) { Status.SUCCESS -> { observeState.value?.data?.let { it.results?.let { list -> InitView(modifier = modifier, list, itemClick) } } } Status.LOADING -> { } Status.ERROR -> { } } } } } @Composable private fun AppBar() { TopAppBar( elevation = 5.dp, backgroundColor = purple200, modifier = Modifier.height(55.dp) ) { Text( text = stringResource(id = R.string.app_name), color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Bold, modifier = Modifier .padding(8.dp) .align(Alignment.CenterVertically) ) } } @ExperimentalFoundationApi @Composable private fun InitView( modifier: Modifier = Modifier, movieEntityList: List<MovieEntity>, itemClick: (MovieEntity) -> Unit = {} ) { Column( modifier = modifier .statusBarsPadding() .background(MaterialTheme.colors.background) ) { LazyColumn( state = rememberLazyListState(), modifier = Modifier.padding(4.dp) ) { items( items = movieEntityList, itemContent = { item: MovieEntity -> MovieItemView(modifier = modifier, item, itemClick) }, ) } } } @Composable private fun MovieItemView( modifier: Modifier = Modifier, movieEntity: MovieEntity, itemClick: (MovieEntity) -> Unit = {}, ) { Surface( modifier = modifier .fillMaxWidth() .padding(4.dp) .clickable( onClick = { itemClick(movieEntity) }, ), color = MaterialTheme.colors.background, elevation = 8.dp, shape = RoundedCornerShape(8.dp) ) { ConstraintLayout(modifier = Modifier.padding(16.dp)) { val (image, title, content) = createRefs() Image( modifier = Modifier .constrainAs(image) { centerHorizontallyTo(parent) top.linkTo(parent.top) bottom.linkTo(title.top) } .fillMaxHeight() .aspectRatio(1.0f), contentDescription = "image", painter = rememberImagePainter(data = "https://image.tmdb.org/t/p/w500/" + movieEntity.poster_path, builder = { crossfade(true) }) ) movieEntity.title?.let { Text( text = it, style = MaterialTheme.typography.h2, textAlign = TextAlign.Center, modifier = Modifier .constrainAs(title) { centerHorizontallyTo(parent) top.linkTo(image.bottom) } .padding(8.dp) ) } movieEntity.overview?.let { Text( text = it, style = MaterialTheme.typography.body1, textAlign = TextAlign.Center, modifier = Modifier .constrainAs(content) { centerHorizontallyTo(parent) top.linkTo(title.bottom) } .padding(horizontal = 8.dp) ) } } } }
That is all about the live data and view model in Jetpack Compose. In our next tutorial, we will learn more about the Jetpack Compose.
Happy Coding.
I am a very enthusiastic Android developer to build solid Android apps. I have a keen interest in developing for Android and have published apps to the Google Play Store. I always open to learning new technologies. For any help drop us a line anytime at contact@mobologicplus.com