In Android development, organizing your code in a structured way is essential for scalability and maintainability. One of the most popular architectural patterns for Android apps is MVVM (Model-View-ViewModel). MVVM helps you separate concerns and enhances testability, making your code more manageable. In this post, we’ll learn how to display a custom list using MVVM architecture with Jetpack Compose, Kotlin, and Coroutines.
What We Will Build
We will create an Android app using Jetpack Compose to display a custom list of data (for example, a list of users or items). The app will use MVVM to separate the UI, data handling, and business logic. The app will also use Kotlin Coroutines for asynchronous operations like fetching data from a network or a local database.
Prerequisites
- Basic knowledge of Jetpack Compose, MVVM architecture, and Kotlin.
- Android Studio installed with Kotlin support.
Steps to Build the Custom List App with MVVM and Coroutines
Let’s break this down into the following steps:
- Create the Data Model: Define the data you want to display in the list.
- Create the Repository: Handle data fetching, either from a network or a local database.
- Create the ViewModel: Expose the data to the UI and manage UI-related data.
- Create the Composables: Use Jetpack Compose to create the UI that observes the ViewModel.
1. Create the Data Model
The data model represents the data structure that will be displayed in the list. In this case, we will define a simple User
data model.
data class User(val id: Int, val name: String, val email: String)
This model will be used to represent individual items in the list.
2. Create the Repository
In the MVVM architecture, the Repository is responsible for managing data and fetching it from different sources (e.g., network, local database). Here’s an example of a simple repository using a suspended function (asynchronous operation) to simulate fetching data from a remote API.
import com.example.test.data.User
import kotlinx.coroutines.delay
class UserRepository {
// Simulate fetching data asynchronously
suspend fun getUsers(): List<User> {
// Simulating network delay using a delay function
delay(2000) // Simulating a network call delay
return listOf(
User(1, "John Doe", "johndoe@example.com"),
User(2, "Jane Smith", "janesmith@example.com"),
User(3, "Alex Johnson", "alexjohnson@example.com"),
User(4, "Mark john", "markjohn@example.com"),
User(5, "Bill laste", "billlaste@example.com"),
User(6, "Deep lucifer", "deeplucifer@example.com"),
User(7, "Kora Frank", "korafrank@example.com"),
User(8, "Atticus Austin", "atticusaustin@example.com"),
User(9, "Eve Reese", "evereese@example.com"),
User(10, "Scarlet Frost", "scarletfrost@example.com"),
User(11, "Nyla Martin", "nylamartin@example.com"),
User(12, "Tony Moran", "tonymoran@example.com"),
User(13, "Rudy Escobar", "rudyescobar@example.com"),
User(14, "Waverly Clay", "waverlyclay@example.com"),
User(15, "Zev Velez", "zevvelez@example.com")
)
}
}
This repository will return a list of User
objects after a simulated network delay.
3. Create the ViewModel
The ViewModel is responsible for handling the UI-related data and business logic. It acts as a bridge between the Repository and the UI. The ViewModel
will call the UserRepository
to fetch the list of users asynchronously using Kotlin Coroutines.
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.test.data.User
import com.example.test.respository.UserRepository
import kotlinx.coroutines.launch
class UserViewModel : ViewModel() {
private val userRepository = UserRepository()
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> get() = _users
fun fetchUsers() {
viewModelScope.launch {
val userList = userRepository.getUsers()
_users.postValue(userList)
}
}
}
In the UserViewModel
:
- We use
viewModelScope.launch
to launch a coroutine in the ViewModel’s scope, ensuring that the coroutine will be cancelled when the ViewModel is cleared. - We fetch the data asynchronously from the repository and post the data to the
LiveData
object (_users
), which is then observed by the UI.
4. Create the Composables
Now, let’s use Jetpack Compose to create the UI. We will display the list of users in a LazyColumn
, which is the Compose equivalent of a RecyclerView
. The UI will observe the LiveData
exposed by the ViewModel
.
Main Screen Composable
import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun UserListScreen(userViewModel: UserViewModel = viewModel()) {
// Observe the users LiveData from ViewModel
val users by userViewModel.users.observeAsState(emptyList())
// Fetch users when the composable is first displayed
LaunchedEffect(Unit) {
userViewModel.fetchUsers()
}
// Display the list of users
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(users) { user ->
UserItem(user)
}
}
}
@Composable
fun UserItem(user: User) {
val context = LocalContext.current
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clickable {
Toast.makeText(context, user.name, Toast.LENGTH_SHORT).show()
},
elevation = CardDefaults.cardElevation(4.dp),
shape = RoundedCornerShape(8.dp)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(text = user.name, style = MaterialTheme.typography.headlineMedium)
Text(text = "Email: ${user.email}", style = MaterialTheme.typography.bodyMedium)
}
}
}
Key Points:
- We use
observeAsState
to observe changes to theusers
LiveData. - The
LazyColumn
is used to display the list of users in a scrollable view. It’s efficient and only renders the items that are visible on screen, similar toRecyclerView
. - Each
UserItem
is displayed in aCard
with padding and elevation.
Putting It All Together
In your MainActivity
, you can set the UserListScreen
composable to display the data.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
TestTheme {
// Provide UserViewModel using the viewModel() function
UserListScreen()
}
}
}
}
Now, when the app runs, it will fetch the list of users asynchronously using Kotlin Coroutines and display the list in Jetpack Compose.
Conclusion
By following the steps above, we successfully built an app that displays a custom list of users using MVVM architecture, Jetpack Compose, and Kotlin Coroutines.
This pattern is not only clean and testable but also provides a seamless separation of concerns between the UI, business logic, and data management. Using LiveData
, viewModelScope
, and Jetpack Compose ensures a reactive UI that updates automatically when data changes, all while maintaining good performance and keeping the codebase manageable.
With Jetpack Compose's declarative approach, building UIs becomes easier and more intuitive, and combining it with MVVM and Coroutines ensures that your app remains scalable and maintainable.
I appreciate you taking the time to read my latest post! ๐ I’m always looking to improve, so your feedback would be incredibly helpful. What did you think of the content? Was there anything that could be better explained? Let me know in the comments! ๐๐”. Happy coding! ๐ป✨