Alright, so let's continue on now
updating our application, our calculator app.
Back in MainActivity, our first OnClickListener
is appending the button's caption
to the edit text widget's contents.
So, that's at the start of onCreate.
You can see it doing that lines 15 through 18.
So, the new number widget's going to
have its values set from our
view model's LiveData object instead.
So what we need to do here is call a function
in the view model to tell it that
a digit button was tapped.
I'm gonna call the function digitPressed
and we'll be pasting the button's caption as an argument.
We haven't written it yet, so we're gonna
get an error when I actually do this,
in fact we'll get two errors because
we haven't subscribed to the view model yet.
But one thing at a time,
so what we're gonna do first is delete these two lines,
we're gonna replace that with viewModel
which again, we haven't defined yet, .digitPressed.
Then we need two parentheses, v as Button,
closing parentheses, .text.toString,
then we've got the closing parentheses
for toString and the closing parentheses for that line.
I'm tempted to modify the other two listeners
to avoid jumping back and forth between
our two classes, but I think it's clearer
if we stick to one thing at a time.
So let's write the digitPressed function
in CalculatorViewModel next.
We'll go back to the CalculatorViewModel class
and I'm gonna put this before
the perform operation function.
So I'm gonna start by typing fun digitPressed,
parentheses, caption: String.
And the terms of the code,
you're gonna start by putting if,
parentheses, newNumber.value
is not equal to null, closing parentheses.
We'll open a code block, then we're gonna type
newNumber.value is equal to newNumber.value
plus caption, and we'll need to add
an else to that code block,
else gets its own code block,
newNumber.value is equal to caption.
I've effectively, digitPressed,
that's this digitPressed function.
It does the same thing as the code
we've removed from MainActivity.
It appends the button's caption
to the current value of a newNumber LiveData object.
We do need a null check there that we've got
on line 20 because value may well be null.
If we don't check for null, we'll get the string null
appearing at the start of our number.
Alright, so back to MainActivity,
and let's now look for, or look at, the next listener.
This next listener is the listener
that responds to one of the operand buttons being tapped.
We'll do something similar here.
We'll call a function in the view model
and pass it the caption of the button.
There's a lot more code in this function
so instead of deleting it, what I'm going to do
is cut it to the clipboard.
That'll save typing when we create
the corresponding function in the view model.
What I'm going to do first is the OnClickListener,
that's the first line there,
I'm going to type viewModel, which again
we haven't yet defined, the function
will be called operandPressed,
which again we haven't created yet,
two parentheses, v as Button,
closing parentheses, .text.toString.
We've got the two parentheses,
to close off toString and then the parentheses
to close off the operandPressed function call.
I'm gonna copy the rest of this text
or rather, cut this text out of this listener
as mentioned, and we're gonna go back
to our CalculatorViewModel class.
We're gonna write the operandPressed function.
I'm gonna put that below the digitPressed function.
We'll start by typing fun, that's operandPressed.
It's got op called String, parentheses,
and you have to write curly braces
to make it a function and we'll paste in
that code from our MainActivity class.
Once again here, we want to make sure
we click cancel when asked to import
the synthetic imports, so I'm going to cancel that.
The reason is firstly, we don't need
that first line because we're not
going to be dealing with the UI.
Alright, so I'm gonna cancel that.
Firstly, this first line here,
we don't actually need that because
we've already cast the view to be a button
and passed its text already as the op argument
so I'm gonna delete that.
There are a couple of things to fix though.
Firstly, newNumber, which we're using here
on line 30 is a LiveData object in here
so we need to get its value rather than its text.
I'm gonna change text there to value
but also with a safe call operator.
I'm gonna remove the .toString
and call .toDouble instead.
That's the first part, then down here
where it's newNumber.setText,
we need to change that to value as well,
equals, and get rid of the parentheses
around the two double quotes.
It's possible that newNumber may not have a value,
which is why we're using a safe call,
why I added a safe call before
converting it to a double on line 30.
As a result, I've got an error here, when we try
and pass value to the performOperation function.
Value is actually a nullable double
and pendingOperation expects a non-null argument.
What we can do to resolve this
is wrap the call in a null test
and let Kotlin perform a smart cast for us.
To do that, after the value line on 30,
I'm gonna put if, parentheses,
value is not equal to null,
open a code block, I'm gonna paste in
that performOperation into that code block.
The last change to this function
is similar to what we've done earlier.
We're attempting to now update
the operation widget and we should
actually be using another LiveData object instead.
Let's declare that LiveData object
below the other two, so we're going to need
a val here for operation, val operation
is equal to, it's also a MutableLiveData object.
It's a string, you'll have to write in
less than and greater than signs,
and add parentheses as well.
Then once we've done that, we can change
this operation.text to operation.value
to save it to our MutableLiveData object.
Okay, just clean up some lines here.
Alright, so that's two listeners changed.
There's one more listener we need to change.
Let's go back to MainActivity.
This third listener that we need to change.
You're gonna do the same pressers here,
add a call to the appropriate function
in the view model, and cut the code out of here
and paste it into the new function
in our CalculatorViewModel.
Within the buttonNeg.setOnClickListener,
I'm gonna do a call to viewModel.negPressed,
parentheses, of course we getting errors
because it hasn't been created yet.
We're gonna cut out this code,
we're gonna go back to our CalculatorViewModel class
and let's add this function below the other two,
below the digitPressed and operandPressed functions.
This one is negPressed, no parameters for this one.
We'll paste in the code.
Once again, I'm gonna cancel the suggestion
to add the synthetic import.
We'll just move this up so I can see a bit easier.
Fixing the errors, it's the same as before.
We just need to use the value property instead of text.
It's pretty easy there because
all the references there are actually
highlighted in red for us, so firstly
newNumber.text is newNumber.value, delete that out.
NewNumber.setText, change that to value.
That's gonna be equal to, remove the parentheses
around the double quotes and minus sign.
Next we've got newNumber.setText.
We're gonna change that to newNumber.value.
That's gonna be equal now to,
remove the parentheses from doubleValue.toString.
You've still got the parentheses on the end of toString.
Finally, to catch the NumberFormatException,
newNumber, instead of .text, it's .value
equals, remove the parentheses,
and leave the double quotes in there.
You can see that we've still got an error here though.
This error here, we're calling the isEmpty function
on something that may be null.
But this particular scenario,
rather than using a safe call,
I'm going to add a null check.
The reason is that Kotlin can't perform
a safe cast on this line down here,
this value.toDouble, so it's also showing
an error there for that reason,
but a null check will fix that error as well.
What we need to do is change this line,
it's currently got if parentheses value.isEmpty.
We'll add a null check in there.
If value is equal to null or value.isEmpty,
or, that's two pipe characters,
that then fixes the remaining errors.
That's our viewModel class finished for now anyway.
We're gonna come back to it to sort out
that problem that I mentioned.
We shouldn't really be exposing
MutableLiveData objects but we'll see
how to deal with that once we've got the app working.
Before leaving though, just make a check here.
Just make sure that the imports,
or check the imports rather, to make sure
that there's no synthetic imports hanging around.
You can see in my case that there's no imports there.
The last thing we now need to do
is to subscribe MainActivity to our CalculatorViewModel.
Going back to MainActivity and specifically in
the onCreate function, we're going to
use the viewModelProviders class
to get a reference to the view model.
This code I'm about to type in
is lifted straight out of that
example code we were looking at earlier.
Just as a warning, we'll actually
get an error as we go through this.
If I type in, below the setContentView,
val viewModel is equal to ViewModelProviders.
I actually want providers here,
but notice we haven't got an option for providers.
We want providers and not provider
but that doesn't appear in the suggestions
but we'll see why in a moment.
I'm gonna continue typing this.
Providers.of, parentheses, this, parentheses,
.get, then in parentheses again,
it's gonna be CalculatorViewModel::class.java, parentheses.
The only difference to the code
in the example is that we used ::class.java
rather than .class because this is Kotlin
and the example we were looking at earlier is in Java.
Well, there's actually another difference,
we've got an error at the moment,
so switching back to that document,
go back to our browser again.
We'll have a look at the top left here.
There's this sub option here,
adding components to your project.
I'm gonna click on that.
That's gonna open up another page.
If you scroll down and have a bit of a look there,
you can see lifecycle, we've got some
information there about dependencies
that are needed for LiveData, lifecycle, and ViewModel.
We haven't actually done this step yet
and that's why we didn't get the suggestion
in the drop down and why we've got an error.
We need to actually follow the advice here
and add the appropriate things to our build.gradle file.
Now, I could have referred you
to this link first and avoided the errors
but it's such an easy thing to forget
that seeing what happens when you do forget,
I think, is very useful.
I scroll down as you saw and a little way
down that page you can see this lifecycle section.
We want ViewModel and LiveData
and you can see that's the top dependency here.
What we need to do again, as I mentioned,
is add to our build.gradle file.
Google started spreading the version numbers out
so here you can see that they've defined
the lifecycle version to be 1.1.1 at the moment
as of the time I'm recording this.
We're gonna copy these three lines,
starting from the def lifecycle_version,
including the comment, and including this
next line, the implementation line.
We'll take a copy of that and we want to
actually go back to our module's build.gradle file.
So I'll open up that, click on our module app.
I'm gonna double click on the build.gradle file.
What we're going to do is paste in those three lines
as the first entry below the dependencies,
or in the dependencies section.
Using that lifecycle_version declaration,
I think, makes it much easier to
update the version of all lifecycle dependencies
at once rather than hunting through the list to find them.
While we're here though, we've also got
this other warning which we've seen
in other projects that the Kotlin-stdlib-jre is deprecated
and should be replaced with jdk instead.
At the moment, Android Studio is still
generating new projects with that deprecated dependency
and they're obviously going to change
at some point so if you don't see this warning,
then you can ignore this bit, that's fine.
But I'll update the deprecated dependency
which is easy to spot because it's got
a line striking it out.
I'm gonna change, we're gonna delete the re
in jre and change that with jdk and we need
to sync now and that should have fixed the error.
It also removed the warning,
so I'll wait for that to finish,
then we'll go back and check MainActivity.
You can see that it was downloading some components.
It's actually now synced and we can close down that window.
You can see we've no longer got a warning there after jdk.
If you go back to MainActivity,
we've now got an option there to import.
It may come up automatically or
you may need to import it yourself.
Once you do that, you can see that
we've now got ViewModelProviders being valid.
You can see the import has now
been added onto line 3 as well.
Now, often that'll actually auto-import
for you automatically, but you can see
in my case I needed to do an Alt + Enter
to import ViewModelProviders
from Android.arch.lifecycle.
Just do that if it doesn't import automatically.
Now that we've actually imported that,
you will find that ViewModelProviders,
with the S, will appear as a suggestion.
Personally I've got no idea why it was happy
to suggest ViewModelProvider without the S
but not the one we wanted.
After all they both come from Android.arch.lifecycle.
All of this is very new and it's also
in the process of being moved into Android X.
Google are sorting through their support libraries
and there's a blog post about it
that I think is quite interesting to check out.
Life should be a lot simpler after the migration
and we'll no longer have things like
a v7 support library with
a minimum supported Android version.
But that said, I don't recommend that
you switch to these new libraries yet
and you can find more information
about them as you scroll down.
It's talking about the old Android package names
and the new Android package names there.
Again, I don't suggest that you switch
to the new libraries just yet.
If you read through that, you'll find that
this does actually mention the same thing,
to not switch yet from production code.
The next version of Android Studio
will perform the migration of your dependencies
automatically so hopefully Android X
will be out of beta around the time
Android Studio 3.2 is released.
Until then though, stick with
these dependencies that I've shown you.
Alright, so creating an instance
of your view model isn't that easy.
You create, and I'll go back to the code now,
you actually create the static of method
or call the static of method of
the ViewModelProviders class to get
a ViewModelProvider, and then you call
its get method to get the view model type you want.
Here actually what we want is a CalculatorViewModel,
so that's what we specified on line 16 when calling get.
The code to start observing
the view model's LiveData objects is simple.
I'm gonna type that in and then talk about it.
We're going to type viewModel.result.observe
then in parentheses, this comma,
then we want observer, I'm gonna choose this one here.
Specify string, less than and greater than sign
to separate it, and then we want
our left and right curly braces and within there
we're gonna type stringResult
and the arrow, the dash greater than sign,
result.setText and in parentheses
it's gonna be stringResult.
We've got the closing parentheses
closing right curly brace and then closing parentheses.
Right now I need to do a similar thing to newNumber.
viewModel.newNumber.observe,
and we're gonna type this then observer again
and less than greater than,
we're gonna specify string again.
Then we've got our left and right curly braces again
and this time we're gonna type stringNumber
and the arrow which is dash greater than sign,
then newNumber.setText.
In parentheses, it's gonna be stringNumber.
The third line, pretty similar there.
Model., this time it's gonna be operation.observe,
parentheses, this comma, and it's gonna be observer.
Observer again, and again in
the less than and greater than sign
we're gonna put string again
and left and right curly braces.
This time it's gonna be stringOperation
and the dash greater than sign, the arrow,
then we're gonna type operation.text
equals stringOperation.
We set up observer now on each of
the LiveData objects that we're interested in.
The observe function needs an owner,
which will be our activity in this case.
It also needs an observer instance.
Observer is just an interface and you can see there
that we're using landers to provide the callback
that'll be executed when the observed object changes.
If the view model's result LiveData object changes,
our result, which is text, will be set to the new result
and the same thing happens for the other two widgets.
That's the theory, let's see if it actually works.
I'm gonna run the application.
I'll just fast forward this bit
while it's actually starting the emulator.
Okay, we'll just make this a bit bigger.
Alright, so let's do a quick test here.
I'm gonna enter 56 and tap on the modification.
You can see there the display is updated
and we've got the number 56 in the result
and we've also got the modification
as the operation to be performed.
We type seven and equals, you can see
the answer is 392.0.
So far so good, but what about rotating the device?
Let's select division and
I'm gonna rotate the device.
I can see the display there still looks fine,
and if everything's working, 392 should be
divided by the next number.
Let's enter four and equals and you can see that worked.
We got the result 98.0.
We're not saving the state of our app,
the variables for the operation
and the operand, but it still works
after a configuration change.
The view model isn't destroyed
and recreated when the activity is.
When the activity gets recreated,
it subscribes to the view model again
and everything's just as it was
before the activity was destroyed.
That's pretty cool, so I'm gonna finish
the video now just by reviewing our code.
Firstly, looking back at MainActivity.
I think you'd agree that's significantly
easier now, really quite simple.
It really doesn't do anything now
that isn't directly related to the UI.
It's really just setting up,
after the layout is inflated on line 15
there's a call there to the getContentView
on the start of onCreate.
The next bit of code there observes
the LiveData objects and updates
the various widgets when they change.
Everything really there is just
setting up for the listeners.
The listeners don't attempt to do
any processing here, they're just
calling functions in the view model
and the view model actually does all the work.
What we've done is we've separated
the display from the app's logic
and the activity's only concerned with
displaying the data and responding to user interaction.
In the CalculatorViewModel class,
we've got all the code that deals with the app's logic.
This is still a very simple app
and we'll be seeing view models
that do a bit more later.
Notice that the CalculatorViewModel class
doesn't have any knowledge about its activity.
In fact, it doesn't even know
that there is an activity.
We could use it from a fragment
or a service and it would still work.
What it does do is expose LiveData objects
for the results it needs to pass back
and doesn't have to worry about
whether there's something observing them or not.
LiveData takes care of unsubscribing
any observers if they're no longer active.
When they subscribe again, they get
the latest values from the LiveData objects.
Alright, so I'm gonna stop the video here
because this turned into a marathon one.
In the next video, we're gonna make
a slight change to the view model
so that it doesn't expose MutableLiveData objects.
See you in the next video.