BMI Calculator – ViewModel and Data Binding

In this lecture of the BMI Calculator sample application we are going to implement the calculation of the BMI value. For this we gonna use ViewModel and Data Binding.

In this chapter you will see how you can use ViewModel and LiveData to avoid having code in the fragements and how you can handle the click listeners using Data Binding.

You will see also what does the Two-way Data Binding means together using MutableLiveDatas.

GitHub

The starter project for this lecture is available on GitHub. Just download it using the below link.

GitHub – branch app_icon

After the download, extract the app from the .ZIP file and import it in Android Studio (File -> New -> Import Project…)

Data Binding

If you are not familiar with Data Binding, then check out our basics tutorial by clicking on the below link.

Data Binding basics

Step 1 - Handling events

As we have talked in the introduction, we are going to remove the event of the click from the fragment. In this application, using Data Binding, we will us LiveData to navigate to the next fragment. 

We are going to subscribe the fragment to the changes in the LiveData and it reacts to them. In our case we should notify the Fragment only once. To solve this issue, we are going to define a new class, an Event wrapper.

You can read more about this approach by clicking on the link below.

LiveData with SnackBar, Navigation and other events

So, create a new class in the main source set by clicking on the main package of the app with right mouse button.

In the popup window select “New” then the “Kotlin file/class” option. In the popup window name the new file as “Event”.

Copy and paste the below class into the Event.kt file.


The Event class

The advantage of this approach is that the user needs to specify the intention by using getContentIfNotHandled() or peekContent(). This method models the events as part of the state: they’re now simply a message that has been consumed or not.

Jose Alcérreca

Step 2 - Extension functions

For this application we are going to define 3 extension functions.

Using Kotlin’s extension function feature we can create for the already existing classes new custom functions.

You can read more about extension function under the link: Extensions

So first, create a new package in the main source set with the name of “utils”.

Then, inside the utils package, create a new Kotlin file: ExtFun.kt

First paste into our new file the below code, then we will check what are the functions doing.


Extension functions

We are going to use the first functions to simplify the SnackBars’ event.

The setupSnackbar function will create an observer using the Event class what wave already defined. In this function you can see the call of the getContentIfNotHandled() method, which provides us to fire the observer of the LiveData only once in case of data changes.

After the setup we can use the showSnackbar method on the View class.

The last method, getNowInString() will simplify for us the conversions of the Date class to a specific String format.

Step 3 - ViewModel of add new BMI

In the third step we are going to define two LiveDatas and a function. The first LiveData will contain the result of the calculations, the second one is to show data on the screen, The function will calculate the BMI and will contain a text field content checker also.

Variables

First we will make a LiveData with an Event. It will notify the user using a SnackBar. We will use it to show this SnackBar, whether the field of the height or the weight is incorrect.

So now, open the AddNewBmiViewModel::class from the addnewbmi package and paste there the below lines.


LiveData of Snackbar's text

Thenafter comes the Event of the calculation. For this we will use the same logic as in case of the SnackBar.

So, copy and paste the below code after the SnackBar‘s lines.


LiveData of the calculation

Next, we are going to use Two-way data binding. With it we can set the value of the LiveData using the eg. text attribute and set a listener that reacts to changes in that attribute.

Again, paste the below line after the previous ones.


Two-way data binding

Later on, in the layout file we will add them to the EditTexts’ text attributes of the height and weight values.

The last variable of this ViewModel is a simple instance of the BMI data class. We gonna pass this variable to the ResultFragment, when we press the “Save BMI” button.

lateinit var bmi: BMI

Functions

Next, we are going to define 3 functions.

The first function will check the correctness of the EditTexts. When they are incorrect, then a SnackBar will show on the screen the error message.


Check values

The second function will create for us an instance about the BMI class. With this, we will set the initial value of the above created bmi variable.


create BMI

The third function will finally calculate the BMI value and tell for the calculation’s Event, thet it was successful.


Calculate the BMI

Step 4 - Two-ways data binding

In this step we are going to add the above created ViewModel to the AddBmiFragment‘s data binding.

First, open the add_new_bmi_fragment.xml file from the res->layout folders. Then add the below variable to the beginning of the layout file. It should be between the tags.


viewmodel variable

Now, open the AddBmiFragment::class from the addnewbmi package. There we have defined the binding and viewmodel variables. In this step we will start using the viewmodel variable from the layout file.

So, add the below line directly before the return line in the onCreateView() method.

binding.viewmodel = viewModel

Now go back to the add_new_bmi_fragment.xml file and add the below attribute to the views.

To the et_height EditText :

android:text=“@={viewmodel.height}”

To the et_weight EditText :

android:text=“@={viewmodel.weight}”

To the btn_calculateBmi Button :

android:onClick=“@{() -> viewmodel.calculateBmi()}”

Step 5 - The EventObserver

Do you remember the Event class? When we have defined it we were talking about that this class will handle indside of our app the navigation. Now it is time to show you how does it work in action.

Navigation

Inside of the Event.kt file we have defined an EventObserver::class, which extends the Observer::class. Because of this, in the AddBmiFragment::class we can use it to observe the changes of the calculateBmiEvent LiveData, what we have in the AddBmiViewModel::class.

So, inside of the setupNavigation() method wrap the navigation with the observer, insteed of the click listener of the button.

The final code looks like below.


wrap the navigation

Show the SnackBar

Next is the setup of the extension function for the SnackBar.

So, copy and paste the below function into the AddNewBmiFragment::class .


Setup the SnackBar

… and call it in the onActivityCreated() method.

setupSnackbar()

Step 6 - ViewModel of the result BMI

In the last few steps we are going implement the code which will calculate for us the value of our BMI and show it on the result screen.

For this we gonna implement the body of the ResultViewModel::class. So, open it from the result package and add the below variables to it.


LiveDatas of the result viewmodel

The resultBmi will hold the passed BMI, what the result screen get from the add new BMI screen.

The resultCalculatedBmi Float variable is the calculated BMI value.

Then, in the next function we are going to set the above defined variables’ value. So, paste below them the next functions.


Set the values of result

For now, you can close the ResultViewModel::class.

Step 7 - Bind the result

Next is to bind the above created variables with the layout. So, open the result_fragment.xml file from the res->layout folders.

Then, as we have done it before, paste the below variable into the tags.


Variable of the result layout

Thenafter, because the viewmodel variable is already available in the layout, we can start using its content.

The first is to set the text of the tv_result TextView. This will show us the calculated BMI value.

So, copy and add the below line to the tv_result TextView.


Set the result's value

Note how we are using strings! If you open the strings.xml file from the res->values folders, then you can see the string calculated_bmi which value is “%.1f”. This means, thet the Float value gonna be converted to String with 1 decimal.

Next, open the ResultFragment::class from the result package and add the ResultViewModel to the viewmodel of its layout.

Paste the below line into the onCreateView() method, before its return line.

binding.viewmodel = viewModel

The first .viewmodel is the variable in the layout file. the second viewModel is defined the ResultFragment::class.

Step 8 - Safe args

In this step we are going to retreive the passed BMI, when we pressed the “Calculate BMI” button on the AddNewBMIFragment. For this, we are going to use the Safe Args library from Navigation Component.

We would like to get back this argument in the ResultFragment::class. So, open it from the result package and add to the beginning of this class the below member variable.

private val safeArgs: ResultFragmentArgs by navArgs()

Ok, we are almost done with this chapter. The last step is to pass the BMI from the safeArgs to the ResultViewModel using its getBmi() method.

So, paste the below line into onActivityCreated() method.

viewModel.getBmi(safeArgs.bmi)

Run the app

Now it’s time, run the app. If you have done everthying correctly, then you should have SnackBars if the textfields are incorrect and a calculated BMI value on the result screen.

GitHub

The source code is available on GitHub under the branch viewmodel_and_databinding, check out using the below link.

GitHub – branch viewmodel_and_databinding

Questions

I hope the description was understandable and clear. But, if you have still questions, then leave me comments below! 😉

Have a nice a day! 🙂

 


 

Follow and like us:

Click to rate this post!

[Total: 0 Average: 0]

Leave a Reply

Your email address will not be published. Required fields are marked *