Alright so let's have a look at the onPostExecute method now.
Now we're going to do things slightly differently here to how we did it in the top 10 downloader app.
now in the top 10
downloader, our async task class was very tightly coupled to the other classes in the app.
So I'm going to open that project so you can see what I mean.
Now you may not be aware of this but Android Studio
lets you have several projects open at once, although if you haven't got much RAM it may not be a good idea, but
you can actually do it. So if I come up here to the file menu in Android Studio, I can select Open Recent, and
I can see a list of the projects that I've opened previously.
I'm going to select top 10 downloader, and if I hadn't worked with it recently I could have used the File
Open, and browsed to the project directory instead.
Now we've got this prompt here, and Android Studio then asks if I want to open it in this window which'll close
the current project, or in a new window.
So I'm going to choose new window, so I can have both projects open at once, and by the way that can be useful,
and you may want to do that so you can have my source code open at the same time as yours,
if you're checking for problems. Alright so I'm going to click on New Window.
Alright, so what do I mean when I say tightly coupled. Well firstly let's have a bit of a look here.
I'm just going to close down some of these things. So have a look at MainActivity, and we come down here to the onPost
Create, right down the bottom here, the onPostExecute method for our download data class.
That's actually in here, it's our companion object, so onPost
Executed as you can see here on line 134. Now what it's doing is that it's creating a parse applications
object, and telling it to pass the data that was downloaded. Now that's fine,
there's nothing wrong with that approach, but it does mean that downloading the data and parsing it, treat
it as separate operations.
In other words the same class that downloads the data also starts the parsing.
Now even though it doesn't perform the parsing itself, it's still responsible for deciding what to do
with the parsing.
So it really can only be used for downloading RSS feeds in XML format, because attempting to
use a parse applications object with any kind of data, will cause problems.
So that's what I mean when I say tightly coupled. Although the main purpose of the download data class is
to download data,
it can really only be used to download XML data that conforms to the RSS specification.
Now to give you an idea of why that's not ideal,
the version of that download data class, in the previous version of the Android Java course that this
course is based on, was used by nearly 40000 students. So as well as all the testing I did when developing it,
it's also been extensively used by other people,
so it's proven to be a reliable class. Now it'd be great to just lift it out of that app, and use the
same class in this one because it's been proven to work, but because of that tight coupling, I can't use
it to download our json data without making some changes,
and as soon as I change it, it then has to be tested again in its new form.
So in other words we lose the benefit of all those hours of testing that it had.
Now in this case it's quite a simple class,
so that's not a great hardship, but as you work on more complex code,
it's worth keeping your classes uncoupled.
That way they can be re-used when you need the same functionality in another app.
So for our new app, our flickr app, we're going to write the rest of the download class a bit differently,
to allow it to be used whenever we want to download any sort of text data.
So I'm going to close this top 10 project, top 10 downloader project.
So we're back now into the FlickrBrowser app, and specifically in our getRawData Class, and let's see what we
can do differently in the onPost
Execute function in this class. So this is the one that was responsible for the closed coupling in the top 10 downloader app,
because it created an instance of the parser and also initialized the adapter for the list view.
Now both of these had nothing to do with downloading data, which is what this class should focus on.
So we're going to keep this class focused on just downloading the data. Before that though, I'm going to log the parameter
we get, and then I'm going to run the program and I'm going to make sure that the doInBsckground method is working
OK. I'm also going to remove the call to super dot onPostExecute,
because it really doesn't do anything.
And I can actually check this by holding down the command key,
or control on a PC, and then clicking on this super method, so up here, in the onPostExecute,
I can click on that to view the source code. So as you can see there, for the onPostExecute source code,
there's no code to execute. The onPostExecute method's blank.
Now there's also an annotation up here to suppress the unused declaration warning,
let's see this on line 427,
which is another good indication that this method in the async task base class doesn't do anything.
So in other words it's there purely to provide the method for us to override. The async task will call our method
if there is one, but there's no point us calling that super method, because in this case it's empty.
Now it doesn't normally do any harm if you leave it in,
but you'll see later why I've been pretty keen to remove it this time.
So let's go back to the getRawData class in the onPostExecute.
So I'm going to delete that line, and instead replace that with a logging line, Log.d parentheses
TAG comma, double quotes onPostExecute called, parameter is and dollar result.
Next we need to execute the async task from MainActivity, and specifically from the onCreate function.
Let's go back to main activity, a bit more space here, so in onCreate, down here, after the setSupportActionBar line, we're
going to type val getRawData is equal to GetRawData, parentheses, then getRawData.execute, and in parentheses,
and in there what I'm going to do is paste in the link for the json feed that we had from our browser
last time, and paste it in there because that's the argument that's going to be sent to the getRawData
class, or specifically the execute function. OK, so let's run this app.
Now we won't see anything on the emulator screen because it's still going to show Hello World, but we will
be able to see things working in the log cat, starting with the security exception when I run the app.
Let's actually have a look, look in our log cat, and we've still got our filter on.
I'm going to take the filter off, because we also want to see the code in getRawData showing, and we can see
now some of the logging, but we can also see this error, 'doInBackground Security exception Needs
permission? Permission denied, missing Internet permission'.
And after that onPostExecute called you can see that parameter is doing background, and we've actually got
the message being returned there. So in other words our catch block's working to trap the permissions error.
So to test the download I need to add the Internet permission in the manifest,
so let's go ahead and do that. I'll open our manifest, double click it.
Alright then from there, we're going to add our permissions as we've done previously, then above the applications line
it's going to be, uses permission, and the name is going to be, we're going to just type INTERNET, and press enter there
and we're going to close off that tag.
Remember to select that from the drop down rather than typing it, to avoid any chance of problems
coming up there, with errors, with mistyped name.
Alright so I'm going to run this again now. We should make a bit more progress.
Let's have a look, and the emulator won't do anything different but we'll be able to see some more output,
and you can see now, some data there obviously being returned.
And by the way if we want to clear up things to make it a bit easier,
you've seen before how we can filter. We can use the pipe character to do a double filter so
main activity, pipe, get raw data. That can be easier to only return the logging for the things that we're interested
in looking at.
And note if we scroll down to the bottom that not all data will be logged, because there's a limit on how much you
can log in a single log call,
but if anything had gone wrong with the download we'd have an exception, and we saw an example of an
exception with a security exception, before we fixed that in the manifest.
We don't need to see all the data to know that it is working.
So in other words, at this point our doInBackground function's working. Now it's a good idea to test
functions like this as you go along, that way if there's an error,
you've already narrowed it down to a small bit of code, and
that makes debugging a lot easier.
Alright, so how do we keep the get raw data class uncoupled from the other classes in the app? So we know
that at some point we're going to want to process the data that's been downloaded.
If you want to decouple this class then the onPostExecute function isn't the place to kick off that processing.
So what we really want to do is notify the calling class that the data's being downloaded, and somehow
make the data available to it.
So the basic idea is that the calling class uses get raw data to download the data from some URL.
So get raw data then does the downloading, and when it's finished it calls the class back, using what's
known as a callback method. Now that sounds a lot like what happens when a button gets tapped.
The class that wants to know about the tap, creates an object that's got a function to respond to the click. The
calling class then passes that object to the button.
These are the onClickListeners that we've been creating to respond to button clicks
in previous apps in this course, but here what we want to do is respond when the download is complete.
So to see how this can work I'm going to create an on download complete function in main activity, and
it's going to be called when the download finishes, and we'll do that part in a moment.
But to start I'll just go back to main activity, and we're going to close down the manifest too for now. And we'll close down our
log cat, and what we want to do is create this new function onDownloadComplete.
So we'll do that in the bottom of the file,
so fun onDownloadComplete parentheses data colon string status colon,
and we'll use a DownloadStatus for that which is our enum, open a code block and the code is going to be, if parentheses
status is equal to DownloadStatus.OK, code block. We're going to log it, so
Log.d parentheses TAG, double quotes onDownloadComplete called, data is dollar data,
else, download failed, Log.d parentheses TAG comma onDownloadComplete failed with status dollar status,
error message is dollar data. So this function's got two parameters,
the data, which may be an error message if things go wrong with the download, and the status.
Now we're going to throw all this code away, but sometimes you have to do that while developing.
I've created this function just to get things working, and see how it works, and once we've got the basic
implementation working we'll change it quite a lot.
Now developing small bits at a time like this, lets you test your code as you build the app up.
Alright, so we now need to tell the get raw data class about this onDownloadComplete function so it knows to call it.
Now we could add a method called something like, set download complete listener, or something along those
lines, which is how the Android widgets do it. Now for a widget, it makes sense to have a separate function
to set the listener.
Because they can either be created in code, or by inflating XML, their constructor wouldn't be an appropriate
place to set the listener, but get raw data is only created to download data, and it's got no user
interface or anything like that to complicate matters.
So in that scenario it makes sense to set the listener at the same time as we create the object, by passing
the listener in the constructor.
However many students struggle with this concept of call back functions.
So what I'm going to do is start off, in the next video, in the same way as a button, by adding a
setDownloadCompleteListener function, and get raw data.
So let's do that in the next video.