Blog Infos
Author
Published
Topics
, , , ,
Published

A ViewModel is one of the core Jetpack libraries: It’s almost unthinkable not to use it inside your Android project nowadays. It does the nifty thing where it survives configuration changes, and furthermore, still dies when it is supposed to die. Did you know you can also use ViewModels in Android Auto and Car App library? In this article I will show you one way to do it. Without further ado, lets go over some basics.

Android Auto Programming, courtesy of Copilot.
Understanding what makes a ViewModel special

Lifecycle and Android developers have a special relationship. It doesn’t matter how much love you give your Lifecycle, it will always find a way to hurt you. After years of letting developers struggle by themselves, Google finally created some architecture guidelines and Jetpack libraries to lighten the load. ViewModels are special: You can use them often in Activities or Fragments. But did you know that the Jetpack Navigation library also has excellent support for ViewModels? You can scope your view model to live and die together with an activity, fragment, a navigation graph or even with a navigation destination! The power they use under the hood, is the ViewModelStoreOwner. Google provided lots of components to scope your ViewModel, making sure they get created, survive configuration changes and get cleared whenever they need to disappear.

A ViewModelStoreOwner is the essence of this: It holds the references to all the ViewModels of a lifecycle aware component. An Activity is a ViewModelStoreOwner, and if an activity is destroyed (not recreated), then it will automatically clear all the ViewModels it has in its store. Same goes for Fragments, or NavBackStackEntries: They hold the ViewModels for your pleasure and destroy them when no longer needed!

Motivation to use ViewModel in Android Auto

In our application we extensively use MVVM. We use StateFlow’s to hold our states and viewModelScope to do any of the heavy lifting. When at first we needed to implement Android Auto functionality, I directly knew I had to find a way to keep our MVVM pattern alive! Also, we reuse actual functionality from our normal app inside our Android Auto parts: It would be extremely satisfying to reuse the exact same ViewModel in our Composables, as well as in Android Auto.

Android Auto/Car App library Screens

Android Auto has the following setup:

  1. You provide a CarService and register it in the manifest. From here Android can run your app on an Android Auto ready car when you plug it in.
  2. Your car service provides a Session, your own implementation.
  3. Your Session provides a Screen
  4. You build templates within a Screen
  5. If you want to redraw the content, you must invalidate the screen
  6. You can navigate from one screen to another.

Here you can find a lot of resources and I advice you to check out the code lab. I found it very informative. The basic code will look something like this:

/**
  * Service, needs to be registered
  */
class ReservationCarAppService : CarAppService() {

  override fun createHostValidator(): HostValidator {
    return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR
  }

  override fun onCreateSession(): Session {
    return ReservationCarSession()
  }

}

/**
  * Really it is this empty :) Maybe it can do some more stuff I don't know about
  */
class ReservationCarSession : Session() {

 override fun onCreateScreen(intent: Intent) = ReservationCarScreen(carContext)

}

/**
  * This is where the magic actually happens
  */
class ReservationCarScreen(carContext: CarContext) : Screen(carContext) {

  override fun onGetTemplate(): Template {
    // Return your template
  }

}
Missing ViewModelStoreOwner

The Screen is lifecycle aware, but sadly it is not a ViewModelStoreOwner. Luckily, we can fix that! A ViewModelStore owner is easily constructed and doesn’t cause a lot of overhead. However, you must be sure to understand what you are doing! The Screens luckily don’t suffer from orientation changes: Once they are created they are kept alive in the Session. So, it would be good to create a ViewModelStoreOwner, and clear it (and therefore the ViewModels it contains) when the lifecycle reaches destroyed state. The easiest way to do that, is by simply starting up a coroutine in the lifecycleScope, and then wait until it gets cancelled and call the clear() method:

fun Screen.getViewModelStoreOwner(): ViewModelStoreOwner {
  val viewModelStoreOwner = object : ViewModelStoreOwner {
    override val viewModelStore = ViewModelStore()
  }
  lifecycleScope.launch {
    try {
      awaitCancellation()
    } finally {
      viewModelStoreOwner.viewModelStore.clear()  
    }  
  }
  return viewModelStoreOwner
}

class ReservationCarScreen(carContext: CarContext) : Screen(carContext) {

  val viewModelStoreOwner = getViewModelStoreOwner() // Voila!

  override fun onGetTemplate(): Template {
    // Return your template
  }

}

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

No results found.

Jobs

Hooking it up with Koin DI

Now you can use this to get your ViewModel! We use Koin for our dependency injection. So we can inject a ViewModel using the existing getLazyViewModelForClass function. Sadly it is a deprecated method, but it should be safe to use as the and it does what we want it to do: Get the ViewModel from the root Koin scope. So we can write a quick function to fetch a lazy ViewModel:

inline fun <reified T : ViewModel> Screen.viewModel(
  viewModelStoreOwner: ViewModelStoreOwner = getViewModelStoreOwner(),
  noinline parameters: ParametersDefinition? = null,
): Lazy<T> {
  @Suppress("DEPRECATION")  
  return getLazyViewModelForClass(  
    clazz = T::class,
    owner = viewModelStoreOwner,
    parameters = parameters,
  )  
}

class ReservationCarScreen(carContext: CarContext) : Screen(carContext) {

  private val viewModel by viewModel<ReservationDetailsViewModel>()

  override fun onGetTemplate(): Template {
    // Return your template
  }

}

Note that in my case it is safe to create a new ViewModelStoreOwner any time I inject a ViewModel. This is due to the fact that my car screens only have one ViewModel attached to them, so this is the lazy man’s way to not having to keep a reference to it.

Updating the Screen

We will use the fact that a Screen is alive until it isn’t and using the lifecycleScope we can collect any state that we want. Now the important part is, that we invalidate the screen any time our data changes. Then in the onGetTemplate method, we simply get the value from the state in the ViewModel and update the screen.

class ReservationCarScreen(carContext: CarContext) : Screen(carContext) {

  private val viewModel by viewModel<ReservationDetailsViewModel>()

  init {
    lifecycleScope.launch {
      viewModel.screenState.collect {
        invalidate()
      }
    }
  }

  override fun onGetTemplate(): Template {
    val builder = PaneTemplate.Builder(
    
    return Pane.Builder().apply {
      setLoading(viewModel.screenState.value.isLoading)
      setTitle(viewModel.screenState.value.title)
    }.build()
  }

}

Great success! Every time the state updates, the screen invalidates and we can generate a new template. I will write a follow up post on how to write a Compose-like API to generate templates in an easy way, and provide some amazing GIFs as well on that one. Until then, I hope this article provided you with some insights. Please let me know what you think in the comments, and as always, if you like what you see, put those digital hands together! Joost out.

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
READ MORE
Menu