DEV Community

Cover image for ViewModels should not expose suspending functions
Subbu Lakshmanan
Subbu Lakshmanan

Posted on

ViewModels should not expose suspending functions

Why

Sometimes the developer may add a suspend keyword to a function that calls another suspending function as that's the first auto-suggestion/warning provided by the IDE.

IDE warning

IDE makes it easy for the developer to auto-fix the warning by adding the suspend keyword to the function.

IDE auto-suggestion

But, this breaks the rule Classes extending "ViewModel" should not expose suspending functions.

Views should not be responsible for directly triggering coroutines. Hence, ViewModel classes should prefer creating coroutines instead of exposing suspending functions to perform some piece of business logic. This approach allows for easier testing of your application, as ViewModel classes can be unit tested, whereas views require instrumentation tests.

What to do

When you find a function in a ViewModel which breaks this rule, follow these steps to refactor it:

  1. Find the need for the suspend keyword in the function, i.e., check if the function is calling any other suspending function
    • e.g., listOfPlants.collectLatest is a suspending function
  2. See if you can wrap the suspending function call in a coroutine & use the appropriate scope & dispatcher
    • e.g., viewModelScope.launch can be used to wrap the suspending function call
  3. Remove the suspend keyword from the function
  4. Remove creating coroutines from the caller of the function if it is not needed anymore

How to do

The example,

// In ViewModel
suspend fun listenForUpdates() {
    listOfPlants.collectLatest { plants ->
        plants.filter { it.isFavorite }.let { favorites ->
            // do something with favorites
        }
    }
}

// In Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    lifecycleScope.launchWhenStarted {
        viewModel.listenForUpdates()
    }
}
Enter fullscreen mode Exit fullscreen mode

could be refactored to,

// In ViewModel
fun listenForUpdates() {
    viewModelScope.launch {
        listOfPlants.collectLatest { plants ->
            plants.filter { it.isFavorite }.let { favorites ->
                // do something with favorites
            }
        }
    }
}

// In Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    lifecycleScope.launchWhenStarted {
      viewModel.listenForUpdates()
    }
}
Enter fullscreen mode Exit fullscreen mode

References


Dev Diairies image

User Feedback & The Pivot That Saved The Project

🔥 Check out Episode 3 of Dev Diairies, following a successful Hackathon project turned startup.

Watch full video 🎥

Top comments (0)

AWS Q Developer image

What is MCP? No, Really!

See MCP in action and explore how MCP decouples agents from servers, allowing for seamless integration with cloud-based resources and remote functionality.

Watch the demo

đź‘‹ Kindness is contagious

Discover fresh viewpoints in this insightful post, supported by our vibrant DEV Community. Every developer’s experience matters—add your thoughts and help us grow together.

A simple “thank you” can uplift the author and spark new discussions—leave yours below!

On DEV, knowledge-sharing connects us and drives innovation. Found this useful? A quick note of appreciation makes a real impact.

Okay