Displaying a Custom List Using MVVM Architecture with Jetpack Compose in Android Kotlin

 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:

  1. Create the Data Model: Define the data you want to display in the list.
  2. Create the Repository: Handle data fetching, either from a network or a local database.
  3. Create the ViewModel: Expose the data to the UI and manage UI-related data.
  4. 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 the users 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 to RecyclerView.
  • Each UserItem is displayed in a Card 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! ๐Ÿ’ป✨

0 comments:

Post a Comment