Alright, so moving on. When you open a
HTTP connection, the server sends back
a response code. So our code is going to
get the, or does get the response
code for the connection and logs it in
the logcat, and that's this code here,
on line 53. Now you may have seen HTTP
response codes, when you try to visit a
page that doesn't exist on a website, and
you see 404 not found in your browser.
Now 404 is the response code that a web
server returns, if it can't find the page
that was requested. Now if you want to
check the HTTP response codes, then
google HTTP response codes. We'll just do
that quickly now. You get a lot of
information - HTTP response codes. And you
can see here, we've got one from
Wikipedia - List of HTTP response codes,
but there's also another one, and this
one here - Status Code definitions. That's
actually the definitive reference, so I'm gonna
click on that just briefly and open it,
although I don't expect you to memorize it.
But with that said, the Wikipedia entry,
which I'll just go back to now,
that's more accurate and a bit more
readable. So I'd suggest that that would
be a good place for you to start, and we
can see if we just scroll down here, the
various areas; 404, The requested resource
couldn't be found, or could not be found
but maybe available in the future.
Subsequent requests by the client are
permissible. So basically, going back to
our code. The code itself creates an
InputStream - that's this line here, line
56 - and then uses it to create an Input
StreamReader on the next line. Now the
InputStreamReader is then used to
create a BufferedReader on line 58. Now
this can look a bit horrible, but these
IO classes have been designed to work
together in this way. And they're part of
the Java.io package, so this is
standard Java, not Android specific code.
Now we're going to be using a lot of
Java classes from our Kotlin code,
especially as most of Android is still
written in Java. Now reading the code
upwards, the BufferedReader buffers the
InputStreamReader, and it'll be the
BufferedReader that we'll use to
actually read the
XML. Now the InputStreamReader uses an
InputStream object as the source of its
data, and the InputStream is created
from our open HTTP connection. Now you
wouldn't normally see the code written
like that. Because these classes are
designed to work together, it's more
usual to chain the method calls together.
So what I'm going to do is make a change
so that you can see the more usual way,
and comment out the
current code so that you can compare it.
So I'm going to come up here before the,
these three down here; inputStream,
inputStreamreader and reader, and I'm
going to comment those out. And
instead, I'm going to replace that with one
line, which will be val reader equals
BufferedReader, then InputStream,
parentheses InputStreamreader, then
another set of parentheses, connection
dot InputStream, and close the two
parentheses there.
So the previous three lines of code can
be replaced with a single line that does
exactly the same thing.
Now one other neat thing about the way
that these i/o classes are designed to
work together, is that closing the
BufferedReader will automatically close
the InputStreamreader, and when the Input
StreamReader closes, it'll close its
InputStream. And we'll see that working
later when we add the code to close
things down. Now if you need to, you can
find out more information about the
BufferedReader for Android, so I'm going
to take a copy of a link and paste it
into the browser. And as usual, as always,
the links are in the resources section
for the video. So that's the Android
documentation that I've pointed you to,
but the Bufferedreader is a Java class,
so you'll also find the Oracle
documentation on the java.io package
useful, if you want to find out more
information. Now there's one more
exception that we should deal with.
Now this one's necessary because Android
requires an app to be granted
permissions to do certain things, such as
accessing the Internet. Now Google
changed the way that permissions worked
in Android Marshmallow, so to deal with
them properly isn't always just a trivial
change to the manifest. Now I'm going to
deal with the permissions, and also cover
catching the security exception in a
later video.
So we've now got a connection to the
Internet, and we've set up the reader so
we can read in the download at XML. All
we have to do at this point, is read the
data in and append it to our String
Builder until there's no more data left
to read. Now a BuffereReader actually
reads characters, not strings, so we need
to set up a character array that's going
to be filled with characters from the
buffer. So I'm going to add the code to
read the data inside the try block, after
line 59, down here. So I'm going to start
with val input buffer equals Char
Array, 500 in parentheses. Then we're
going to do var charsRead equals 0.
Then we're gonna do while parentheses
CharsRead greater than equal to 0,
closing parentheses and open a code
block. Then we're gonna do charsRead equals
reader.read inputBuffer. And on the
next line, if charsRead is greater
than 0, code block xmlResult.append
String parentheses inputBuffer comma
space 0 comma space charsRead. I'm
putting an extra space between all these
variables, just for ease of
readability, but you don't necessarily
have to do that. Now I made a mistake
there - while charsRead is less than
equal to 0 should, of course, be greater
than equal to 0, because you want to keep
testing and reading data while there's
some data there. So basically, I've
created the inputBuffer character array,
here on line 61, to store 500
characters. Now that number's a bit
arbitrary, and if you expected a large
download, you may be able to improve
performance by increasing it. The size of
the buffer used internally by the
BufferedReader on Android is currently
8192
bytes, which is okay, but if we were
reading from disk than a larger value
would be a good idea. TCP packets are
typically much smaller than 8k, so we
read 500 characters at a time and let
the Bufferedreader deal with getting the
data off the inputStream and holding it
in the buffer for us. Now the while loop,
which was starting on line 63, that'll
keep going around until the end of the
inputstream's reached. Now if the
Bufferedreader's read function returns a
value less than zero, then that signals
the end of the stream of data, and the
loop terminates. So that's why we've got
the condition on line 63; charsRead
greater than equal to zero. We're only going to
loop as long as there is data available. Once
there's no more data in the input
Stream, we're going to stop, otherwise
charsRead will hold a count of the
number of characters read from the
stream. Now it is possible for this to be
zero, but just because there was nothing
currently available to read, that doesn't
mean more data won't come down later.
So we don't stop looping if nothing was
read, but there's also no point appending
nothing to xmlResult. So the code checks
that charsRead is greater than zero -
doing this on line 65 - before
appending the characters to the result.
Now technically, that check for chars
Read greater than zero is redundant, with
the Java classes we're using. If there's
no data available, but we haven't yet
read it all, the reader will block - what
that means, it'll wait until there is
more data available. So with these
particular classes, we'll never get zero
bytes returned. However, not all readers
work the same way, and 0 may be returned,
even though the end of the stream hasn't
been reached. So I've coded this
defensively, so that we don't stop
reading just because there's no data
currently available. More may turn up a
few milliseconds afterwards. Now, although
we're writing this code for a particular
purpose - in this case, to download an RSS
feed from Apple - you may actually want to
re-use it for different purposes. So
that's why I've catered for the
possibility that there's nothing yet
available to read. And note also, that we
don't try to copy 500 characters from
the inputBuffer - this is the code on
line 66 - and that's
because there's no guarantee that 500
characters were available. Instead, we're
only copying the number of characters
read, which is the value of charsRead,
or the charsRead variable.
So each time around the loop, more
characters will be appended to the xml
Result, until there's nothing left to
read. When the end of the inputSteam's
been reached, the loop terminates and we
close the BufferedReader. As I mentioned
earlier, that will automatically close
the InputStreamReader and the input
Stream. And we can check the source for
the BufferedReader's close method, by
holding down the command key on a Mac or
Ctrl key on a PC or Linux machine. So we
click BufferedReader first, to open that,
and if we come down and have a look for
the close method. Now unfortunately, I'm
recording this video before Google have
made the API 26 source available, but
by the time you watch this, you should be
able to jump into the source. Until then,
I've got a couple of slides that I want
to show you, of what it would look like
with the current source code. So this is
the source, now on screen, for the
BufferedReader, showing it's close
method. So the comment does state that it
closes the source reader and we can
see the call to in.close in that
method. Now the reader variable, in, is
actually of type Reader. But we're
using an InputStreamReader, so we can't
just click on the in.close method
here to check the source. We can go back
to our source and command click or
control-click on the InputStreamReader
class, to see the source for that and
then search for the close method - well we
could do that if we had the source code.
But instead, what I'm gonna do is show
you, on the next slide, what that would
end up being. So once again, looking at
that code you can see that it closes the
source InputStream. Alright, so let's
move back to Android Studio.
So as you can see, there's no one need to
close all the i/o objects that we've
used - closing the BufferedReader takes
care of everything else for us. So let's
go ahead and add the code for that. So if
I come down here and add reader dot
close, and that's the method to close the
BufferedReader. Alright, so the last
thing we need to get our download XML
function to do, is to return the results.
Now if everything went well,
we can just return the xmlResult, but
as it's a StringBuilder, we need to
first convert it to a string. And I'm
also going to log the number of bytes
received from information and to help
debugging, if things go wrong. So below
that reader.close method call, let's
add Log.d, and in parentheses, TAG, and
then double quotes, Received dollar sign
and left and right curly braces, xmlResult
dot length. Then outside of the right
curly brace, the word bytes, and then we
go to return xmlResult.toString.
Okay, so you can see that we've actually
done that now. Now, if there are any errors
caught in our catch block, we should
return an empty string. So we saw the
doInBackground function check for an
empty string after calling downloadXML.
So what we're going to do is, down here,
bearing in mind that we've already
returned the results if we found some,
but after the closing catch block, we're
going to return an empty string - two
double quotes. So basically, the
comment we'll add there, It it gets to here,
there's been a problem.
Return an empty string. So that just tells
us there was some error that has been
caught by our try catch blocks, and we're
going to return an empty string for that
reason. What we're doing now is, we'll try
giving this a bit of a test run, so let's
go ahead and do that.
Now, we've actually got an error here, and
the error is Unresolved reference:
downloadXML. So at first glance, you might
be looking at that and wondering, Well,
clearly I've got the names correctly
entered here. Why do we have this error?
Well the reason is that we need to have
this private function inside our private
class - our DownloadData class - and I've
actually got it outside of it. So I'm
going to take a copy of that entire
function. I'm going to do a cut to take
it out of its current location, and I'm
going to come up here now, make sure
that it's now in our companion object - our
private class for DownloadData - and we
need to paste it in our private class. We
need to come down here, after the doing
Background method and paste it down
there, and once we do that, we should find
the error disappears. The private function
downloadXML there is okay, and we
haven't got any other errors showing. So
we should now be able to try running
this again, so I'm just going to - well
actually, I'll leave, I'll close this down
because we're going to open logcat, and
we'll try running it again. And we should
find, this time, it'll work, but we are
going to get errors the first time. Al
right, so we've got the app working, but if
we open logcat and take a bit of a look,
though we've still got the filter set, so
that's a good thing at the moment. So as
I mention, we're gonna get errors and we
have got errors, and that's a good thing
at the moment, because we're getting a
chance to test the catch blocks in
downloadXML. So you can see we've got
the onCreate called, onCreate: done, doing
Background: starting with a URL goes here.
But then we've got this message here,
downloadXML: Invalid URL no protocol: URL
goes here, Error downloading and onPost
Execute: parameter is, an empty string. So
the error we're getting - Error
downloading - if we come back and have a
look at our code. Well the one
above it is Invalid URL no protocol:
that relates to the MalformedURL
Exception - that's the error message that
is being generated on line 73. And as
a result of that, the doingBackground
function is also reporting error
downloading, for the fact that
because it failed, ultimately, because of
that invalid URL. So let's actually go
back
and provide the correct URL to the Apple
10 top feed of free apps. I'm going
to go back to the browser. I'm gonna go
back. go back here. I'm gonna go back
until I find the link, which was several
videos ago. There it is there. You can go
back to the original Apple link if you
haven't got it on your browser, which I had
in the previous video, and I'll also put
this link in the resources section. I've
taken a copy of that back to Android
Studio, and we're gonna go back up now to
find the dot execute method call, and
send the right arguments. At the moment,
it's here at downloadData.execute.
Let's paste in the correct value
there, which is that URL.
So basically, that's in the onCreate
function, which is the DownloadData
dot execute, which invokes or starts
the AsyncTask. So the onCreate
method creates a new instance of the
DownloadData class, then calls it's dot
execute method. And that causes Android
to call the doinBackground method on
a separate thread and passes this
parameter that we've just pasted into
the execute function call, into the doin
Background function. Now I haven't given
the app permission to access the
internet yet, so if I run it now, we
should see the security exception being
thrown, and it's caught because we've
finished our catch blocks by catching
any exception that hasn't been listed
earlier. But before I run it again, I'm
going to clear the logcat by clicking on
this trashcan over here onto the left
of the logcat pane, just so it's easier to
find the results from this run, rather
than building up a huge log and having
to scroll. In general, it's a good idea to
clear the logcat before testing your app,
especially if you're going to be pasting it
into the Q&A discussions. That way, you're
only posting in entries that are
relevant to the current problem. Now the
current version of Android Studio now
clears the logcat when apps run, but that
doesn't always work, so clearing it
manually isn't a bad habit to get into.
So we go ahead and re-run this again now.
We should find we get the security
exception, and you can see here,
Permission denied missing internet
permissions, in parentheses. So that
doesn't look good but it is because we
haven't given the app permission to
access the internet yet. Now it's worth
seeing what that
looks like, and if I hadn't included this
catch-all - and this is the code I'm
talking about here, down for the catch
exception on line 76 - if we hadn't have
added that, we would have got a complete
stack trace and the app would have
crashed. So it's worth seeing what that
looks like, so I'm going to comment out
the code that catches all other
exceptions. So let's go ahead and do that.
So I'm just going to comment those two
lines out there and leave the
bottom closing code block, to make sure
the code is still valid. And if you run
that again now, this time the app has
crashed, as you can see there, and then we
look at the logcat - we've now got a
stack trace with a fatal exception for
AsyncTask 1. Now all this stuff in the
logcat is a dump of what happened when
the program crashed. The
entries in red are a stack trace, and
although it probably looks very
confusing, it's also extremely useful to
find out why your program crashed. So
this first entry, as I mentioned at the
top, is fatal exception AsyncTask hash
1. Now the #1 is just a number
generated by the Android code. If it
set two background tasks away, then
there'd be a hash 2 and so on. So
don't worry about the actual number you
see there - it'll just keep counting up if
you restart the app without terminating
it first. The bit we want to look at is a
bit further down, and the quick way to
spot what's immediately relevant is to
look for the block of code that
contains blue links. As I scroll down, and
I might need to scroll across to see
it, you can see these blue blue links
over here, to the right-hand side. So
these relate to line numbers in our own
code. So much of the rest of these
entries are coming from the Android
framework, or the Java runtime, but the
blue links relate to our code, so they're
the bits we should really be focusing on.
so that block that starts here - let me
scroll up and have a bit of a
look - Permission denied missing Internet
permission. And we can see that if
we scroll down here; Main
Activity.kt line 51 in our main
activity. So, that in turn, was called from
line 38, as you can see here, and
sometimes it's useful to track back
through the code like that, to see how
you got to the code
that actually caused the error. In this
case, though, it's a little bit simpler.
There's no need to trace back because it
wasn't the way that doInBackground was
called that caused the error - it was the
lack of internet permissions. So make a
note of that exception - security
exception - and in the next video, we'll
actually catch that exception to prevent
the program from crashing. And we'll also
have a look at Android permissions. A
major change was introduced in Android
6 Marshmallow, and continues to apply
in Android 7 and 8, and we'll see
how to fix the cause of that error. So,
see you in the next video.