Alright, so as I mentioned at the end of the last video, we found that many students can struggle with this concept
of callback functions. So what we're going to do is start off by doing things in the same way as a button.
So we're gonna set up or add a setDownloadCompleteListener function and get raw data
first. Let's go back to that class,
and at the top, just above the override fun onPostExecute, in other words just after the download status
definition on line 19, we're going to add fun setDownloadCompleteListener,
and the parentheses, then it's going to be callback object, colon MainActivity, and add our left and right curly braces as
normal. Then we're going to type listener equals callback object.
Now we're getting an error at the moment because we haven't declared listener.
So let's go ahead and do that.
We'll do that above that line, so private var listener colon MainActivity, question mark,
equals null. And you can see that we've got a warning there about this field leaking a context object.
Ignore that for now because it's only temporary, to show how the callback we're going to create is just the
same as a button click listener.
OK so we know that our onDownloadComplete function's in MainActivity, which is why our setDownload
CompleteListener function defines our main activity type as its parameter.
And you can see that on line 23.
Now listener's going to be null until we call setDownloadCompleteListener to initialize it,
and that's why we've declared it to be a nullable type on line 21.
Now we can call the listeners onDownloadComplete function in the onPostExecute function.
So let's go and add the code for that, after the log, so listener question mark, dot onDownload
Complete, then parentheses, and it's going to be result comma and downloadStatus, the two arguments we're passing to it.
Now we've got an error at the moment because result is a nullable string, and our onDownloadComplete
function needs a non-null string type.
Now onPostExecute's going to be passed the return value from doInBackground,
and we've made sure that doInBackground doesn't return null.
In fact it's return type is the non-nullable string type.
And you can see that again here on line 26. Now changing the function signature to accept a string,
isn't going to cause any problems here.
So we'll do that to fix the error. I'll change that, and you can see the error then disappears.
Now we do have to use a safe call when calling the onDownloadComplete method,
and that's because listener might be null.
In other words it's no guarantee that the caller will call the setDownloadCompleteListener function
before starting the async task. Now that's definitely true, because we're not calling it yet.
Now we need to do that before starting the async task,
in other words, before we call the execute function in main activity.
So let's go back to main activity, and go up to this code, and actually before the call to dot execute, in
between the definition of our get raw data variable, we want to put it in there. So it's
getRawData.setDownloadCompleteListener and in parentheses, this, is the argument.
Now I'll explain what 'this' is in a moment. So let's actually try running it though to make sure it works,
and we'll also open log cat.
Now we can see here if we go back and have a look there's a lot of data there.
And in fact the json data has been logged twice in the log cat,
and you can see there's two versions of that,
but the one we're looking at here is the logged output from onPostExecute. But a bit further down, and you can see
onDownloadComplete is also printing out the json data aswell.
So that's working fine overall.
When the async task finishes executing, it calls its onPostExecute function.
Now the onPostExecute function calls the onDownloadComplete function in the listener, which is a current
instance of our main activity.
And again we can see that here, onDownloadCompleteownload called, and there's our function onDownloadComplete, so
it's being called in there.
And this is exactly the same as what happens when you tap a button. You register your activity, or an anonymous
inner class that it contains as a listener,
by calling the setOnClickListener function of the button.
Then when the button's tapped, the button calls your activities onClick function.
So here what we're doing, we're registering our activity as a listener, according to set onDownloadComplete function.
You can see that code being executed on line 20, and when getRawData finishes downloading, it'll call our
onDownloadComplete function.
That's why we're passing this to the call, to setDownloadCompleteListener in MainActivity.
This refers to the current instance of a class.
Now you can think of Kotlin automatically doing something like,
if I just type it up here, private val this equals MainActivity. So
basically think of Kotlin automatically doing something like that.
Now it doesn't really do that,
and I've got an error when I tried, but that's effectively what this is.
It's an instance of a class, MainActivity in this case, that's running the code in the class.
So by passing this, and I'll just delete this now, so by passing this down here to setDownloadcomplete
Listener, we're telling getRawData that it should call the onDownloadComplete function
in this instance of Mainactivity. Alright, now I'm in danger of labouring this point a bit much, so
I'm going to move on.
We use listeners and callbacks a lot in this course, and I will be explaining it all again in a different
way, in a later section. But it is something that students have struggled with in the past, but it's also
an essential part of event driven programming.
Windows and the various Linux GUI's also make extensive use of callbacks,
so it's not just an Android thing. It's a really good thing to learn and really fundamentally understand
as a developer.
OK so moving on, we're going to change that code. So the base functionality remains the same,
but now what we're going to do is implement it differently.
Now we might want to call our getRawData from different classes, not just from MainActivity classes,
so that means we need to ensure there's some way that the class does have an onDownloadComplete function.
So what we're going to do is change the way we create the getRawData object, by adding this as an argument
when we create it. So at the moment we've got val getRawData equals getRawData parentheses.
We're going to add this as an argument.
We've got an error there because getRawData class is expecting that argument.
But also what we're going to do is now delete this next line, well I'll comment it out at least, as we won't need that
after we make this change. So going back to getRawData,
we need a field to store the object whose method we need to call, and then we'll set that field in the constructor.
So looking at our class definition line on line 17,
we're going to change that, we're going to add parentheses after getRawData, and we're going to type private val
listener colon MainActivity. I'll leave everything else as it is. Now I've declared the callback object to be of
the type MainActivity, so that we can pass any main activity class object to it.
There is a problem with that, and we'll look at that in a minute.
For now we're just getting things working and understanding how it works.
So now that we've done that, we don't need our private listener object here on line 21, and
we also don't need this setDownloadCompleteListener function,
so I'm going to comment that out as well. Alright so our getRawData class is definitely going to have a non-
null listener now.
So therefore we can remove the safe call operator from the onPostExecute method. So over here,
we can actually get rid of that now.
So when the download finishes and the onPostExecute function's called, it'll call our main activities
onDownloadComplete function, and provide it with the data and the status result.
And this is exactly the same as before, but we're just providing the listener object differently. So we're
going to clear the log cat, got it open, clear that and we'll run it again,
probably don't need to see the interface. So nothing's really changed
there, you can see that we've still got the data returned. We can see that onDownloadComplete is being called, and
we've got two copies of the adjacent data showing. So basically things have worked just like last time.
So you can see onCreate being called there, an onCreate ending, and the data being logged in onPostExecute, and after
logging the data onPostExecute, causing onDownloadComplete function of main activity, and a bit lower
down in the log cat, was the data that I'm showing you on the screen now.
That's again the data being logged again by onDownloadComplete.
So that's how callbacks work.
You create a function that the called object will call when something interesting happens.
In this case we gave the getRawData instance a reference to the main activity object, by using this from
inside the main activity class. Now the problem I've just mentioned is that there's nothing to guarantee
that main activity does have an onDownloadComplete method. Now ours does, but none of the other main activity classes
that we've created did.
So the way to deal with that is by defining an interface that the calling object must implement,
so an interface is a binding contract.
So anything that implements our interface guarantees that it'll implement the functions we specify.
Now this might be easier to see rather than talk about, so
I'm going to start by defining the interface in a getRawData class.
We're going to go back to the top here,
close down our log cat window, and
below our download status definition I'm going to type interface, onDownloadComplete, and open up a code
block, and within there we're going to put fun onDownloadComplete parentheses, data colon, String comma status colon
DownloadStatus. So to define an interface we just declare it in a very similar way to declaring a class,
and specify the functions that must be implemented by anything that implements the interface.
Now you don't actually write any code for the functions. That's up to the implementer.
All we're doing is providing the name, parameters and return type if any.
So now that we've done that we can change the type of our listener object to be onDownloadComplete. Now just like with
classes, the convention is that interface names should start with a capital letter,
so the interface that we're going to use is on download complete with a capital O.
It specifies a single method which I've called onDownloadCcomplete with a lowercase o, and you can see that
on lines 21 through 23.
So now that we've done that, we can change our listener type. So at the moment time we've got var listener main activity.
We can change that to OnDownloadComplete.
So now our listener property's of type OnDownloadComplete.
Now if I tried to run the app now we'd get an error, because main activity isn't an on download complete object.
So we have to fix that first.
So when I switch over to main activity, there's an error that has come up now when we create the getRawData
object, because we can't pass an object of type main activity to a constructor that expects on download complete.
So what we need to do to fix that, is to make main activity implement the on download complete interface.
Now in Kotlin, we specify that by adding the interface name after the superclass in the declaration.
So I'll come up here and start doing that. At the moment we've got class mainActivity colon appCompatActivity. I'm going to type comma, and
if I start typing OnDownload, you can see that Android Studio offers the get raw data dot OnDownloadComplete
interface as a suggestion. And you can't really see all of that,
but we can see that the academy dot learn programming package there and I can press enter there, and you can see that's filled
in for us automatically.
So now main activity's declared as implementing the interface and can be used whenever an object of type
on download complete is needed.
So we can't just pass just any old main activity to the constructor of get raw data.
We have to use one that implements the interface.
And that means that our get raw data class can be sure that the object that it's been given does have
the correct function for it to callback. Now if we scroll down and have a bit of a look down here,
we can see that we've got an error here,
and that's because we haven't marked on download complete as overriding the interface function, and that's something
that Kotlin insists on. I'm going to type in
override there, hope that keeps it happy, and I'm going to say a bit more about that in a minute, but
I'm going to run the app again first just to make sure it's still working.
Now it looks to me that everything's working fine. We've still got our two versions of the json data being
outputted, and it seems that all the other functions are being called back correctly.
So that looks to be working fine.
So let's have another look at this on download complete function in main activity.
Now we've already typed that function in rather than using the code generator, and it didn't have override
specified. Now Kotlin insists that you mark overridden functions with
override, and you saw that when I typed override there, the error disappeared.
So override tells the compiler that the function that follows it is overriding an existing function
in its superclass or an interface. Using override allows the compiler to check that the function has the
correct name and the correct number and type of parameters. Android Studio also uses it to show these
errors and warnings, over here on the right hand side, the right hand margin.
Let's say for example that I typed the function name wrong. If I change it to have a capital L instead for download,
and that by the way, is quite an easy mistake to make,
but you can see when I do that we're suddenly getting errors. We've got an error here, and also an error up here as well.
So the first one is this error here, 'onDownLoadComplete overrides nothing'.
That means that no function with that name in the appcompat activity base class, or the on download complete
interface, exists. There's no function of that name.
And there's also an error in our base declaration
up here. We're getting that error because the main activity no longer implements the correct function for
the interface.
So the override keyword allows Kotlin to check so that we don't make mistakes like this.
So I'm going to go back and correct the spelling to onDownloadComplete to fix that error, and we'll finish the
video here. In the next video we'll add the code for parsing the json data, so I'll see you in the next video.