Reusing the ViewModel from the external Layout

by Filip

Android Layouts tend to get quite large. The navigation bar at the top, main content in the center, drawer on the left side, and so on. If you're writing all these components in one file, you are going to have a hard time navigating through the code. The usual solution to this problem is to split the layout into multiple files.

Each file is then imported into the main layout via the “<include/>” tag. If no special logic is applied to these views and they are only used once or in navigation purposes, then no further modification is needed.

But what happens when you integrate a ViewModel in the extended layout? You will probably bind data to listen for LiveData changes to update UI. The problem comes around when the layout is reused on multiple places and ViewModel type changes. The same function needs to be called but from different ViewModel. The application won’t compile and binding error will be thrown. The solution lies in class inheritance. In order to see this in action, let’s make an example app.

We will build the app that has only one function. To change the main background color. A button will be placed within the layout which triggers this change. Here is the code for the MainActivityViewModel:

1 2 3 4 5 6 7 8 9 class MainActivityViewModel(): ViewModel() { private val _isDarkTheme = MutableLiveData<Boolean>(true) val isDarkTheme: LiveData<Boolean> get() = _isDarkTheme fun changeColor() { _isDarkTheme.value =!_isDarkTheme.value!! } }

We defined a LiveData object that is responsible for UI changes. Function changeColor() updates the background. Following layout is from MainActivity XML:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?xml version="1.0" encoding="utf-8"?> <layout> <data> <variable name="viewModel" type="i.xxxx.viewmodel_demo.MainActivityViewModel" /> </data> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@{viewModel.isDarkTheme() ? @color/colorPrimaryDark: @color/colorPrimaryWhite}" android:orientation="vertical" tools:context=".MainActivity"> <include layout="@layout/footer" app:viewModel="@{viewModel}" /> </FrameLayout> </layout>

The FrameLayout is the one whose background changes. With the help of ternary operator, we are updating the background based on the LiveData value from the ViewModel.

1 android:background="@{viewModel.isDarkTheme() ? @color/colorPrimaryDark: @color/colorPrimaryWhite}"

As you see from XML there’s a viewModel variable reference, and at the bottom, we are providing an include tag. The “@layout/footer” hosts a button that is responsible for triggering the main function. The XML for the footer is:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="i.xxxx.viewmodel_demo.BaseViewModel" /> </data> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:onClick="@{() -> viewModel.changeColor()}" android:text="Change app theme" /> </layout>

The button has a click-Listener which calls the viewModel’s function. But, wait a second. What’s the type of this ViewModel? Looking at the top of the layout, there’s a different type. In this case, it is BaseViewModel. This is a place where class inheritance comes into play. We are building a parent ViewModel that will hold functions that need reusing.

Create a new Class that extends a ViewModel and implements the common functions.

1 2 3 open class BaseViewModel(): ViewModel() { open fun changeColor() {} }

Now, there are 2 pending updates that need to be done in MainActivityViewModel. Firstly, it must extend the BaseViewModel.

1 class MainActivityViewModel(): BaseViewModel() { .... }

Secondly, the function changeColor() needs to be prepended with an override modifier in order to call the function from the parent class with our custom logic.

1 override fun changeColor() {...}

Build & Run. Code available here.