JetPack Compose With MVVM
This article is about creating an app with Jetpack Compose. We are going to follow basic MVVM architecture and will use Retrofit library and . And for data, we will use the https://www.themoviedb.org/ api’s
Jetpack Compose is a modern toolkit designed to simplify UI development. It is fully declarative, meaning you define your UI by calling series of functions that transform data into a UI hierarchy. And one more important thing about compose is when the underlying data changes framework automatically recalls compose functions and updates the UI.
Before going into details I hope you know the basics about compose and know how to use compose(If no, then I highly recommend you to go through the basics). Attaching some relevant links related to compose
https://developer.android.com/jetpack/compose
https://developer.android.com/codelabs/jetpack-compose-basics
SetUp
You will need the Android Studio Arctic version to work with jetpack compose. you can download the beta version from below link. Jetpack compose won’t work with default android studio which you may be using.
Start a new android studio project and select Empty Compose activity, give your app name and package name details.
Go to app build.gradle file and make sure compose is enabled
buildFeatures {
compose true
}
We have a data layer and UI layer for this project. Data layer will contain all data fetch from themoviedb and mapping to our model. Ui layer will have the composable functions. There are 100’s of articles related to data fetch using retrofit, so I’m not going to discuss that.For reference, I’m attaching the GitHub link for the complete source code.
Before going to the implementation add this library to build.gradle
implementation 'androidx.compose.runtime:runtime:1.0.0-beta09'
Implementation
So by this time, your data fetching part should be done. So next step is to create your ViewModel class. I named it as MainViewModel.
class MainViewModel :ViewModel() {
private val apiService = MovieApiClient.service
private lateinit var repository:MovieRepository
var trendingMovies:List<MovieItem> by mutableStateOf(listOf())
lateinit var clickedItem :MovieItem
init {
fetchTrendingMovies()
}
fun fetchTrendingMovies(){
repository = MovieRepository(apiService)
viewModelScope.launch {
var response = repository.fetchTrendingMovies()
when(response){
is MovieRepository.Result.Success -> {
Log.d("TAG","TEST")
trendingMovies = response.movieList
}
is MovieRepository.Result.Failure ->{
Log.d("MainViewModel","FAILURE")
}
}
}
}
fun itemClicked(item:MovieItem){
clickedItem = item
}
}
in ViewModel class, define API service , repository class.Next thing is to define your movie list and mark it as mutableStateOf(). which is basically an observable type integrated with compose-runtime which we added to our build.gradle.The next thing is create fetchTrendingMovies() function,inside that fetch your data from server and assign the data to the list which we defined above(trendingMovies).
Next step is to create different composable function for different components.
We need basically four composable function
- MainMovieList. — The list view
- ListviewItem
- MovieImageBanner
- MovieMetadataItem
So will create each item first then add it to the mainlist.
First, create a class called ListViewItem.kt
MovieMetadataItem
@Composable
fun MovieMetadataItem(movieItem: MovieItem){
Column(modifier = Modifier
.fillMaxSize()
.padding(start = 10.dp)) {
movieItem?.title?.let {
Text(
text = it
)
Text(
text = movieItem.vote_average,
style = MaterialTheme.typography.body1
)
}
}
This is the two text views showing title and rating ,right to the imageview
Modifier can be used to set padding ,margin,width etc.
Column is used to align items vertically.
for compose functions you have to use @Composable annotation.
Our next component is Image view.for imageview we are going to use Coil(It’s an image loading library backed by google).for that add coil to build.gradle
implementation 'com.google.accompanist:accompanist-coil:0.13.0'
Next is to create a composable function for image
@Composable
fun MovieImageBanner(imagePath: String) {
Image(
modifier = Modifier
.width(180.dp)
.height(100.dp), painter = rememberCoilPainter(
request = Constants.BASE_IMAGE_URL + imagePath
),
contentDescription = ""
)
}
rememberCoilPainter is provided by Coil library,you can pass option to make roundcorner or any image related attributes to rememberCoilPainter.
For more details about coil usage https://google.github.io/accompanist/coil/.
Next is to have a parent function that will add these two
@Composable
fun ListViewItem(
movieItem: MovieItem, modifier: Modifier
) {
Card(modifier = modifier) {
Row(verticalAlignment = Alignment.CenterVertically) {
MovieImageBanner(imagePath = movieItem.backdrop_path)
MovieMetadataItem(movieItem = movieItem)
}
}
}
this listview item have Card and Row to align items horizontally.
Now we have to add this items to the main list based on number of items.
For that create class called ListViewMain.kt
@ExperimentalFoundationApi
@Composable
fun MovieList(
movieList:List<MovieItem>
){
var listState = rememberLazyListState()
LazyColumn(state = listState) {
stickyHeader {
MainHeader()
}
itemsIndexed(movieList){index, item ->
ListViewItem( movieItem = item)
}
}
}
In this Composable function,we are calling ListViewItem function for each and every item of list.itemsIndexed will iterate through the entire list, and we used rememberListState() to update the view during the state changes.
stickyHeader is added to give Title for the page.Which is a normal composable function with a title(It’s a api which can be deprecated in future,so add annotation to function @ExperimentalFoundationApi,otherwise it will give error)
@Composable
fun MainHeader(){
Surface(
Modifier
.fillMaxWidth()
.background(MaterialTheme.colors.background)
) {
Text(
text = "Trending Movies",
style = MaterialTheme.typography.h4,
textAlign = TextAlign.Center
)
}
}
So the data part and UI partis done. Now it’s time to connect between this two. For that create a composable function and pass mainViewModel object .
@ExperimentalFoundationApi
@Composable
fun MainList(mainViewModel: MainViewModel){
MovieList(movieList = mainViewModel.trendingMovies)
}
Final step is to call the above function from your mainActivity.
setContent {
MVVMJetPackComposeSampleTheme() {
Surface(color = MaterialTheme.colors.background) {
MainList(mainViewModel = mainViewModel)
}
}
}
If you run the project now you will be able to see a listview with image and details.
Next step is to add a click event for the list item.We will cover that in Part Two.
Part two can be found from below link
You can find the full source code from github
Happy Reading… Happy Coding..