DEV Community

Cover image for How to create Previews of ViewModel-Dependent Composable in Jetpack Compose
Neeraj Sharma
Neeraj Sharma

Posted on

2

How to create Previews of ViewModel-Dependent Composable in Jetpack Compose

Understanding the Problem

Let's understand the problem we face while creating previews of Viewmodel-dependent Composables.

Previews are lightweight and don't use the whole Android framework to render. Thus, previews have the following limitations

  • No network access
  • No file access
  • Some Context APIs may not be fully available

So the ViewModel that depends on the classes and features that are not available in the preview environment will fail to render with some exceptions.

Real-world scenario where we have a ViewModel-dependent Composable

Let's take an example of a UserProfile screen that fetches user details from a remote server.

ViewModel

The following is a simple ViewModel that fetches user details from a remote server.

class UserViewModel(
    private val userRepository: UserRepository
) : ViewModel() {
    private val _userState = MutableStateFlow<UserState>(UserState.Loading)
    val userState: StateFlow<UserState> = _userState.asStateFlow()

    fun fetchUserDetails(userId: String) {
        viewModelScope.launch {
            try {
                val user = userRepository.getUser(userId)
                _userState.value = UserState.Success(user)
            } catch (e: Exception) {
                _userState.value = UserState.Error(e.message ?: "Unknown error")
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Composabe that depends on the ViewModel

The following is a simple Composable that displays user details.

@Composable
fun UserProfile(
    viewModel: UserViewModel,
    userId: String
) {
    val userState by viewModel.userState.collectAsState()

    LaunchedEffect(userId) {
        viewModel.fetchUserDetails(userId)
    }

    when (userState) {
        is UserState.Loading -> {
            CircularProgressIndicator()
        }
        is UserState.Success -> {
            val user = (userState as UserState.Success).user
            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
            ) {
                Text(
                    text = user.name,
                    style = MaterialTheme.typography.h5
                )
                Spacer(modifier = Modifier.height(8.dp))
                Text(
                    text = user.email,
                    style = MaterialTheme.typography.body1
                )
            }
        }
        is UserState.Error -> {
            Text(
                text = (userState as UserState.Error).message,
                color = MaterialTheme.colors.error
            )
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Preview

The following shows the problem we would have while creating a preview for the above composable.

@Preview
@Composable
private fun UserProfilePreview() {
    UserProfile(
        viewModel = UserViewModel(
            userRepository = UserRepository()
        ), 
        userId = "123"
    )
}
Enter fullscreen mode Exit fullscreen mode

UserRepository uses local and remote sources to fetch user details.
Neither the local nor remote source dependencies are available in the preview environment, so the preview will fail to render
.

Solution

To Solve this issue we can mock the ViewModel or its dependencies with the kotlin Mockk library.
Kotlin mockk is a mocking library for kotlin which is used in unit testing. However, we could leverage the Mockk library to mock the Composable dependencies while creating the preview.

Even if ViewModel is a complex class with multiple dependencies, we can mock it easily by using the Mockk library.

Add Mockk to your project

Add the following dependency to your app's build.gradle file:

note: we use the debugImplementation scope to avoid adding the mockk library to the release build.

dependencies {  
  debugImplementation("io.mockk:mockk:1.13.13")  
}
Enter fullscreen mode Exit fullscreen mode

For more details about Mockk library, check out the official documentation.

ProfileScreen Preview with Mocked ViewModel


@Preview
@Composable 
private fun UserProfilePreview() { 
    val mockViewModel = mockk<UserViewModel>()

    // Define what should be returned when the userState property is accessed
    val userState = remember { MutableStateFlow<UserState>(UserState.Loading) }

    every { mockViewModel.userState } returns userState

    // Define what should happen when the fetchUserDetails function is called
    every { mockViewModel.fetchUserDetails(any()) } answers {
        // give some delay to simulate a real API call
        delay(1000)

        // Simulate a successful API call
        userState.value = UserState.Success(User("John Doe", "john.doe@example.com"))
    } 

    UserProfile(
        viewModel = mockViewModel,
        userId = "123"
    )
}
Enter fullscreen mode Exit fullscreen mode

That's it! Now, when you run the preview, the UserProfile composable will use the mocked UserViewModel. We mock the behaviour of the ViewModel to return a specific state and the behaviour of the fetchUserDetails function to simulate a successful API call.

Conclusion

In this post, we learned:

  1. How to handle Jetpack Compose previews when dependencies are not available in the preview environment
  2. Using MockK library to mock ViewModel and its dependencies in Compose previews
  3. Adding MockK dependency specifically for debug builds using debugImplementation
  4. Creating mock behaviours for ViewModel properties and functions
  5. Simulating API calls and state changes in the preview environment

This approach allows us to create realistic previews of our Composables even when they depend on complex ViewModels or repositories, making the development process more efficient and reliable.

Heroku

Simplify your DevOps and maximize your time.

Since 2007, Heroku has been the go-to platform for developers as it monitors uptime, performance, and infrastructure concerns, allowing you to focus on writing code.

Learn More

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay