Skip to main content

The Two Best Ways to Create a ViewModel in Jetpack Compose

Jetpack Compose has revolutionized Android development by introducing a declarative UI framework that enables developers to build modern, efficient, and maintainable user interfaces. As an essential part of Android’s modern development toolkit, Compose simplifies UI creation and state management, allowing developers to focus more on functionality and less on boilerplate code. Among the many key components of Jetpack Compose is the integration of ViewModel, which plays a vital role in managing UI-related data in a lifecycle-aware manner. In this blog post, we’ll dive deep into the two best ways to create a ViewModel in Jetpack Compose, enabling you to harness its full potential in your projects.

Why Use a ViewModel in Jetpack Compose?

ViewModel, part of the Android Jetpack library, is designed to store and manage UI-related data in a lifecycle-conscious way. It ensures data survives configuration changes, such as screen rotations, making it a crucial component for creating robust Android applications. When combined with Jetpack Compose, ViewModel serves as the backbone for state management, keeping your UI reactive and efficient.

Jetpack Compose’s declarative paradigm thrives on unidirectional data flow and state management. ViewModel naturally complements this by acting as the single source of truth for UI state, ensuring clean separation of concerns and adherence to modern Android development best practices.

Method 1: Using viewModel() from Compose Runtime

One of the most straightforward ways to create a ViewModel in Jetpack Compose is by using the viewModel() function provided by Compose’s runtime. This function is lifecycle-aware and automatically integrates with the lifecycle of the component in which it is used.

How to Implement

Here’s a basic example of how to use the viewModel() function in Jetpack Compose:

@Composable
fun MyScreen() {
    val myViewModel: MyViewModel = viewModel()

    // Observe ViewModel state
    val uiState by myViewModel.uiState.collectAsState()

    // Build your UI based on state
    MyContent(uiState = uiState, onEvent = { myViewModel.handleEvent(it) })
}

class MyViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState

    fun handleEvent(event: UiEvent) {
        // Update state based on the event
    }
}

data class UiState(val someValue: String = "")
data class UiEvent(val action: String)

Key Points

  • Lifecycle-aware: The viewModel() function ensures the ViewModel is scoped to the lifecycle of the composable’s host, such as an Activity or a NavBackStackEntry in Jetpack Navigation.

  • Ease of use: It eliminates the need for manual ViewModel creation, reducing boilerplate code.

  • State management: Compose’s collectAsState makes it simple to observe and react to state changes.

When to Use

Use viewModel() when your composable is hosted within a lifecycle-aware component such as an Activity, Fragment, or a Navigation destination. It’s ideal for most straightforward scenarios where you need to maintain and manage UI state effectively.

Method 2: Using hiltViewModel() for Dependency Injection

For projects leveraging Dependency Injection (DI) with Hilt, the hiltViewModel() function simplifies ViewModel creation and scoping. Hilt handles ViewModel injection seamlessly, allowing you to focus on building your application’s core logic.

How to Implement

Here’s how you can use hiltViewModel() in a Jetpack Compose project:

@Composable
fun MyHiltScreen() {
    val myViewModel: MyViewModel = hiltViewModel()

    val uiState by myViewModel.uiState.collectAsState()

    MyContent(uiState = uiState, onEvent = { myViewModel.handleEvent(it) })
}

@HiltViewModel
class MyViewModel @Inject constructor(
    private val repository: MyRepository
) : ViewModel() {
    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState

    fun handleEvent(event: UiEvent) {
        // Logic to handle events and update state
    }
}

Key Points

  • Seamless DI integration: hiltViewModel() works with Hilt’s DI framework, enabling constructor injection for ViewModels.

  • Scoped lifetimes: Like viewModel(), hiltViewModel() scopes ViewModels to the lifecycle of their host components.

  • Reduced boilerplate: Hilt eliminates the need for manual factory creation, streamlining ViewModel instantiation.

When to Use

Use hiltViewModel() when your project is set up with Hilt for dependency injection. It’s particularly beneficial for complex applications where managing dependencies manually would result in significant overhead.

Best Practices for ViewModel Usage in Jetpack Compose

To get the most out of ViewModel integration in Jetpack Compose, keep these best practices in mind:

  1. Keep UI logic in ViewModel: Avoid placing business or UI logic directly in composables. Delegate these responsibilities to the ViewModel for better separation of concerns.

  2. Use immutable state: Prefer immutable state models and expose them via StateFlow or LiveData to ensure a predictable unidirectional data flow.

  3. Optimize recompositions: Use tools like remember and derivedStateOf to avoid unnecessary recompositions, improving performance.

  4. Test ViewModel logic: Write unit tests for your ViewModel to ensure your state management and business logic are reliable.

Advanced Features

Jetpack Compose offers several advanced features that complement ViewModel integration. Here are a few noteworthy mentions:

  • Navigation: Use NavBackStackEntry as the lifecycle owner when creating ViewModels scoped to a navigation graph.

  • SavedStateHandle: Leverage SavedStateHandle in your ViewModel to persist and restore UI state across process deaths.

  • Custom ViewModel factories: For scenarios requiring additional customization, create your own ViewModel factories and use them with Compose’s viewModel().

Practical Example: Combining Navigation and ViewModel

Let’s consider a scenario where you need to create a multi-screen app with shared state management:

@Composable
fun MainNavGraph(navController: NavController) {
    NavHost(navController = navController, startDestination = "home") {
        composable("home") { HomeScreen(navController) }
        composable("details") { DetailsScreen(navController) }
    }
}

@Composable
fun HomeScreen(navController: NavController) {
    val homeViewModel: HomeViewModel = hiltViewModel()

    // Render UI using homeViewModel
}

@Composable
fun DetailsScreen(navController: NavController) {
    val detailsViewModel: DetailsViewModel = hiltViewModel()

    // Render UI using detailsViewModel
}

This example demonstrates how to use hiltViewModel() with Jetpack Navigation to scope ViewModels to specific destinations, ensuring proper lifecycle management.

Conclusion

Jetpack Compose and ViewModel together form a powerful combination for modern Android UI development. Whether you use viewModel() for simplicity or hiltViewModel() for DI, these methods enable you to build scalable, maintainable, and efficient applications. By adhering to best practices and exploring advanced features, you can harness the full potential of these tools to deliver exceptional user experiences.

Now that you’ve learned the two best ways to create a ViewModel in Jetpack Compose, it’s time to put this knowledge into practice. Start building and experimenting with these techniques in your next project, and see how they transform your development workflow!

Popular posts from this blog

Restricting Jetpack Compose TextField to Numeric Input Only

Jetpack Compose has revolutionized Android development with its declarative approach, enabling developers to build modern, responsive UIs more efficiently. Among the many components provided by Compose, TextField is a critical building block for user input. However, ensuring that a TextField accepts only numeric input can pose challenges, especially when considering edge cases like empty fields, invalid characters, or localization nuances. In this blog post, we'll explore how to restrict a Jetpack Compose TextField to numeric input only, discussing both basic and advanced implementations. Why Restricting Input Matters Restricting user input to numeric values is a common requirement in apps dealing with forms, payment entries, age verifications, or any data where only numbers are valid. Properly validating input at the UI level enhances user experience, reduces backend validation overhead, and minimizes errors during data processing. Compose provides the flexibility to implement ...

jetpack compose - TextField remove underline

Compose TextField Remove Underline The TextField is the text input widget of android jetpack compose library. TextField is an equivalent widget of the android view system’s EditText widget. TextField is used to enter and modify text. The following jetpack compose tutorial will demonstrate to us how we can remove (actually hide) the underline from a TextField widget in an android application. We have to apply a simple trick to remove (hide) the underline from the TextField. The TextField constructor’s ‘colors’ argument allows us to set or change colors for TextField’s various components such as text color, cursor color, label color, error color, background color, focused and unfocused indicator color, etc. Jetpack developers can pass a TextFieldDefaults.textFieldColors() function with arguments value for the TextField ‘colors’ argument. There are many arguments for this ‘TextFieldDefaults.textFieldColors()’function such as textColor, disabledTextColor, backgroundColor, cursorC...

jetpack compose - Image clickable

Compose Image Clickable The Image widget allows android developers to display an image object to the app user interface using the jetpack compose library. Android app developers can show image objects to the Image widget from various sources such as painter resources, vector resources, bitmap, etc. Image is a very essential component of the jetpack compose library. Android app developers can change many properties of an Image widget by its modifiers such as size, shape, etc. We also can specify the Image object scaling algorithm, content description, etc. But how can we set a click event to an Image widget in a jetpack compose application? There is no built-in property/parameter/argument to set up an onClick event directly to the Image widget. This android application development tutorial will demonstrate to us how we can add a click event to the Image widget and make it clickable. Click event of a widget allow app users to execute a task such as showing a toast message by cli...