Alright, so going on to the solution to
the challenge that we talked about at
the end of the last video, the first
thing that we're going to do is add the
refresh option to the menu. Now that's
very simple - we're just going to drag a new
item into the Design, to make sure that
it's below the group and not a member of
it. And we talked about that previously,
so Menu Item, and you can either do it
here or come down here. So it's easy to
do it down here, in the Component Tree
usually. And you can see there, that I've
dragged it so it's actually in the wrong
place, but I can move that out to make
sure that it's no longer in the menu
anymore, and no longer in the menu group
anymore, which is what we want here. Next
we want to change the ID. the ID is going
to be a mnuRefresh, and the title, we'll
make Refresh. Alright, so that's it for
the menu and we'll go back to the code.
Alright, so if we're going to track the URL, so
that we can compare the previous one to
the one that we're about to download,
then we need a variable to store it in.
Now we're also going to be storing the
URL and Limit in a Bundle, so I'm going
to define two constants to use as the keys.
So I'm going to do that at the top of
MainActivity, just below the feedLimit
and feedUrl that we worked on in the
last two videos. So we start with a
private var feedCachedUrl is equal
to, and we'll start with double quotes
invalidated. Then private val STATE
_URL is equal to feedUrl. Then
private val STATE_LIMIT
equals feedLimit. Now I've initialized
the feedCachedUrl String to a value that
can't possibly be a valid URL. Now
anytime we want to force a download to
happen, such as when the Refresh option
is chosen from the menu, we just need to
set it to some value that isn't a
URL. Now anything would do, but I've
just used the word INVALIDATED. Now to
prevent the same URL from being
downloaded unnecessarily,
we just need to compare the cached URL
value with the one that we're about to
download. Now if they're the same, there's
no need to download again. So let's go
down to the downloadUrl function to
do that. So down here, we're going to wrap
this in some code, so we're going to put an
if test here to say if parentheses feed
Url is not equal to feedCachedUrl.
We're going to wrap that code so we're
going to copy this code, paste it in
there. Okay, and I'm going to then put an
else in there, so else Log.d
parentheses TAG comma downloadUrl - URL
not changed. So now with that code, when
downloadUrl's called, it first checks
the URL it's given against the stored
value. Now if they're the same, then
there's no need to download the data
again - it just logs the fact so that we
can see the code working in the logcat.
But of course, if the URL's are different,
then it's going to go through and execute
normally. And importantly, the other thing
we need to do is, we need to update the
value at the end of that; feedCachedUrl
is equal to feedUrl. So basically, after
downloading the data as usual, we're
storing the URL that's just been
downloaded in the feedCachedUrl field,
ready to be compared next time. Alright,
so onto the Refresh menu option. Now all
that has to do, is set feedCachedUrl to
something different to the last URL. So
that's pretty straightforward. So let's
have a look at the option there - the
onOptionsItemSelected. We're going to use
the string INVALIDATED because we've
used that before. So we're going to put
this code here, outside of the R.
id.mnu 10 and mnu 25 tests.
R.id.mnuRefresh, arrow
token. We're gonna put feedCachedUrl
equals INVALIDATE. Alright, so I'm
using that string INVALIDATE, well
actually, INVALIDATED to be consistent. So
using that string again, as that helps to
document what we're doing. So the feedCached
Url has been invalidated,
so the download will then need to
be performed again. Alright, so
that's one part of the challenge
completed. The app should now - shouldn't
now - download the data again, if we select
the same menu item again. So it's worth
testing the app, at this point, to make
sure the code works. If you leave testing
until you've made loads of changes, it
gets much harder to find bugs. You've got
far more code to check through. Running
your app after making each change is a
good habit to get into, and really
does make debugging a lot easier. So
let's actually run this. I'm going to go
back to portrait mode.
Alright, so far. so good. The app loads
the first URL fine, so that's a good
start.
So just to confirm that the menu still
works, we'll try doing a different type -
we'll do Songs. Well that still works - we
still get the top 10 songs. Alright, I'm gonna
open the logcat, and we're going to do a
search, or we're going to, rather, select the
same option again - Songs - and you can see
here in the logcat;
downloadUrl - URL not changed. So that's
working as well. So things seem to be
working pretty good, but to make sure
that the app still uses the same URL and
feed limit, after being rotated, we need
to store the values of the URL and the
feedLimit, when the Activity is
destroyed. So let's go back add
the code for that. And what I'm going to
do is just put the cursor below the on
OptionsItemSelected method function,
and let's get Android Studio to
generate the onSaveInstanceState
function. So I'm going to do my Ctrl-O,
and we want onSaveInstanceState.
Now, remembering there's two versions
of this function. We want the one
that's just got a single parameter. That
we're looking at currently - that's got
two options, so I'm gonna press the down
arrow to go to the second one with the
single parameter. Press Enter.
Alright, I've added that
between the onOptionsItemSelected
and onDestroy, so that onDestroy ends up
being the last function in the class. So
we're going to now write some code in
there. So we're going to start by
removing the question mark from Bundle.
We're going to leave the super dot
onSaveInstanceState line in. We're going
to put outState.putString, and it's
going to be STATE_URL comma
then feedUrl. Then on the next line, out
State.putString STATE_
LIMIT, feedLimit - and that shouldn't be
a string, it should be an int. Let's
change that to an it because, of course,
feedLimit is a number. Okay, that fixes
that. Now we saw code like that before, in
the calculator app - it just
stores our two fields so that we can
call them back again, when the Activity's
recreated. Now the bundle's passed in as a
nullable type, but we know that it won't
be null, and as a result, I changed the
function signature by removing the
question mark after Bundle. Now remember
that data's stored in a Bundle using
key/value pairs. We specify a key when
saving something, and use the same key to
get it back again. And we'll see that
our two keys; STATE_URL and
STATE_LIMIT, being used again
when we retrieve the values shortly. Now
when we saved and restored values
previously, we used the onRestore
InstanceState and function.
Now in that app, though, we weren't doing
anything in onCreate, other than
inflating the calculator layout. So it's
probably a good idea just to review the
Activity Lifecycle. Alright, so on
RestoreInstanceState is called after
the onCreate method. However, we do the
initial download in onCreate. So when the
device is rotated, we need to retrieve
the saved Bundle in the onCreate method,
rather than in onRestoreInstanceState,
because if we wait for onRestore
InstanceState, we won't have the URL
available when we need it. Alright, so
when the Activity's first launched, there
won't be a Bundle. But if the Activity's
restarted because of device rotation, for
example, then the Bundle passed to
onCreate will be non-null, and we can restore
our fields there. So let's actually write
some code now, in the onCreate method, to
achieve this. So there's our onCreate
method and we're going to put some code
after the last logging. Well, actually,
what we'll do is we'll put it after the
start. So we've got the super.on
Create and the setContentView. We'll put
another log entry there, Log.d
TAG parentheses onCreate called, and
then we're going to put our code in here;
if parentheses savedInstanceState is
not equal to null. Add the code block, then
we're going to put feedUrl is equal to
savedInstanceState.getString. Then it's
going to STATE_URL. Then it's going to
be feedLimit is equal to savedInstance
State.getInt STATE_
limit. Now when we're dealing with the
Bundle in onCreate, we have to cater
for it being null, and that's because it
definitely will be, at least some of the
time. Now I know Kotlin tries to do away
with null checks but sometimes they're
valid, even in Kotlin. So here, null is a
valid value for the Bundle. It signifies
that there's nothing to restore. Now
because we've performed a null check,
Kotlin can perform a smart cast. So saved
InstanceState is cast to be an non-null
Bundle, and we don't have to use the safe
call operator when accessing the get
String and getInt methods. Alright, so
we're now ready to test the program to
make sure it meets all the requirements
of the challenge. So let's actually run
this again. Actually, what I'll do is open up
logcat and run it. If logcat doesn't
clear for you, then you'll want to have
cleared it, but you can see, in my
case, it did and then we've got the data -
we've got the logcat showing. So
let's first check a few URLs with
different limits, to make sure we haven't
broken anything that was working. So
I'm gonna go back to the emulator. So we've
got the Top 10 feeds in there, so that's
a good start, and you can see that we've
got the 10 entries in there.
Okay, so that's working alright. Let's now
change that and go for a limit of 25.
Okay, clearly we've got more apps showing
here, so that's working okay as well -
the Top 25. If I go ahead and choose
Songs now, we should get 25 songs, and
that's because the Top 25 was
selected, so click on Songs. And what we
can do is just do a filter for Main
Activity. You can see that the feed
limit was set to 25 and it hasn't been
reset since. Now if we have a look at the
songs, clearly there's more than 10 there.
So that seems to be working nicely as
well. And in terms of the URL, if we
go back and remove the filter, we can see
here by looking in the XML, that clearly
that it's talking about albums and so
forth. So clearly thats Songs, so that's
correct. Now while we've got a Top 25 feed,
it's a good point, or a good time, to rotate
the device to make sure it doesn't
revert to the default Top 10, that it was
doing before. So let's go back and rotate
now. So have a look. It looks to me, we've
still got two heaps of songs there so
everything seems to be working nicely as
well - we've got 25 songs there. Now if I
scroll down a little bit more, we can see
that it's definitely still the songs, and
we can see those on the screen anyway, so
that's working nicely. Now if we go back
and choose songs again, and we can just
do a filter here - MainActivity again - we
can see that downloadUrl, URL is not
changed, which is correct. So that's
looking good and now the last check is
to check Songs again, to make sure that
the data is re-downloaded when the
same type of feed is selected for the
second time. So let's try that - Songs,
and again, download URL not changed. So
that's looking good as well.
Now you may well have solved the challenge
in a different way, but if it passes
these tests, then well done! And
everything's working, so that's the end
of this section. Well, we've actually
covered quite a lot in this section; so
we used an AsyncTask to download data
without blocking the main UI thread, and
also used the built-in HTTP classes to
download data from the internet. Then we
looked at the supplied XML parser
classes to parse XML from the downloaded
data, and store the values we wanted in
our own objects. Now the ListView widget's
designed for displaying lists of data,
and we saw how to connect that to a
basic adapter. We then went further and
created our own custom adapter, to give
more control over how the data's
displayed in the ListView. And we
finished by creating our own menus and
menu groups, and writing code to respond
to the different menu items being
selected. Alright, so now you have a
basic app, you can have a go at changing
things to improve it. So some ideas you
might want to try at this point are;
adding some more of the Apple feeds, such
as the Albums or New Releases feeds. You
could try parsing out the feed title
from the downloaded XML, and displaying
that in a TextView above the list, so that
users have a reminder of which view
they're seing. Perhaps you might want to
try figuring out how to display the
image URL in a text view, and searching
on-line for how to make the links
clickable, so that they open in the
device's browser. Now it's helpful to
watch these videos a few times, to really
understand what's happening, and why we
coded the app in this way. Now you'll
find you pick up different things each
time you watch the videos. Remember also,
that there's a wealth of information
available on-line, so if you're not sure
how to do something, then a quick search
will usually provide loads of
information. Now the Google documentation
is also very useful, even if most of the
examples are currently written in Java,
but I suspect over time, that will change
and we'll start seeing Kotlin examples
as well. So I'll stop the video here, and
end this section, and in the next section
we're going to create an app to view
YouTube videos, using Google's YouTube
API. See you in the next video.