Jetpack Compose Coroutine flow with LiveData/ViewModel in Android

Sharing is caring!

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.

0 0 votes
Article Rating
Jetpack Compose Coroutine flow with LiveData/ViewModel in Android

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Scroll to top
0
Would love your thoughts, please comment.x
()
x