TOMMY MACWILLIAM: In this video, we'll take a look
at how to load some information from the internet into our app.
Now in the last video when we wrote our Pokédex app,
we just hardcoded that list of Pokémon.
But we really want to do is load in all of that information
from some resource on the internet so we don't have to hardcode it ourselves.
The way we're going to do that is by utilizing something
called an API, or Application Programming Interface.
You can think about an API just as some code
that somebody else wrote in a way that's designed
for you to be able to easily use it.
So the API that we're going to use is just going to return some data about
Pokémon, and the format it's going to return that data in is called JSON.
So JSON stands for JavaScript Object Notation,
and it's this format that originally was used with just the programming language
JavaScript, which is a language you can use to write web applications.
But it's really evolved to become a standard format to transfer data,
especially when it's hosted on some server
and you're using some client to read that data.
So here's what JSON looks like.
You can see it's a little bit similar to some of the maps
that we've seen in Java and that it looks like we have some keys,
then there's a colon that separates these keys from some values.
So to start we have these left and right curly braces, and these designate
that this is an object.
And inside of this object, we have three keys, and all these keys are strings.
The first one is course, the second tracks, and the third is year.
Then we have a colon, and on the right side of the colon
we have the value that corresponds to that key.
So the first value is a string, just CS50.
The second value is a list, and this happens to be a list of strings.
And then lastly this third value is a number.
In this case, it's an integer.
And so you can see that with JSON you can have a variety of different data
types inside of the same object.
And as you'd expect, you can also nest objects inside of each other.
So the API that we're going to use is some website
that someone else wrote called pokeapi.co.
So let's check this out.
So here's this API's homepage.
This is pretty nice, because it gives you a lot of information
about the type of data that's going to be returned by this API
in this little sandbox where you can make requests yourself.
So let's take a look.
So it looks like the API request that we just made was
to https://pokeai.co/api--
makes sense-- /v2--
so that probably stands for version two, so there's
some version one around somewhere, but we don't have to worry about that--
/pokemon/ditto.
So ditto is just a Pokémon.
And then here you can see the information
that this API is going to return.
So there's lots of different information here.
Some of these values are arrays, some of them are numbers, some of them
are strings.
If I come down here and click on this view raw JSON button,
you can see the actual JSON that this API is going to return to us.
So when we make a request to that URL, this is the information
that we're going to get back.
So the first thing we want to load from this API is a list of all
of the Pokémon.
So to figure out what I need to do, I'm going
to jump to the documentation for this API.
We're going to click on version two, since that's the latest one.
Why not use that?
And then you can see over here on the left
there's lots of different information about all the different types
of information you can get.
So I'm looking for a list, so let me try clicking on resource lists.
So it looks like here there's--
here's some policies about how to use it.
There's a Slack if you want to ask questions.
There's some libraries.
We're not going to worry too much about those.
Now let's take a look at this resource lists.
So this says calling any API endpoint without a resource ID will
return a paginated list of resources.
Great.
That's exactly what we're looking for.
We're looking for a list of Pokémon.
It also says that I can supply a limit parameter, so if I only want the first,
say, 151 Pokémon, it's really easy for me to do that.
So OK, this is basically all the information I need,
so let's head back to that sandbox and give it a try.
So it said to remove the ID, so I'm going to do that and just delete ditto.
It also said that I can do something like give it a limit equals 151,
so I'll give that a shot and hit Submit.
And now we can see we got a different API response.
It looks like I got this response where this is object with the key of results,
and the value for that key is an array with 151 items.
And if I check out what each item looks like, this looks great.
Looks like we have the name of each pokemon,
and then there's a URL there where I can get more information.
So this is exactly what I was looking for.
So this is the URL that I want to use to request that list of Pokémon.
The library that I'm going to use to make a request to this API
is this library that's provided to us by Google and Android called Volley.
And if I open up the Developer.Android page here
and look at the documentation for Volley,
I can see there's lots of information here.
And in fact there's a tutorial to walk you
through how to make a bunch of different requests,
but we're going to do that together.
The important thing on this page is getting
what this library is called so I can add it to my gradle file.
By default this library isn't included.
Just like we needed to include Recycler View separately,
we'll also need to include this Volley library separately.
So we can just take this and copy and paste it into our build.gradle file.
So let's do that now.
I'm just going to copy this, going to come back over here to Android Studio,
and inside of my build.gradle file I'm just going to add this line.
Again, we get this little information bar about syncing.
So I'm going to do that, download the library from the internet,
and put it somewhere that my project can use.
There we go.
Looks like our sync was successful, so now we
have that library that we need to make those API requests.
One last concept that we're going to see while we're making API requests
is something called a try, catch block.
So Java supports this language feature that's called exceptions.
And an exception kind of, as it sounds, represents
when something goes wrong with your application
that you need to recover from.
So here's what this syntax looks like.
So let's say that we have some object here,
and it has a method on it called something,
and this something might fail in a way that we need to recover from.
And so inside of the body of this it might
say I'm going to throw some exception.
And that means we should stop running our program
and respond to that exception being thrown.
In the case of our API, the exception we're worried about
is if the API returns some JSON that's invalid.
If it's invalid JSON and we can't decode it into some Java objects,
then we need to really recover from that, because it just
doesn't make sense to keep going trying to use some data
if it's not formatted correctly.
So this is the syntax we'll use.
We start by saying try followed by an open brace.
This just says that, inside of these open braces,
something is going to throw some exception.
Then we'll have a catch block, and this catch block
is where your execution is going to jump to if an exception is thrown.
So here we're catching just some exception called exception, that's
a class name, so it's just a type.
And then we'll name that exception called e,
and now we have an exception object.
And from that exception object we can do stuff.
Simplest thing to do is just to print out what that exception is.
So not only print out the message but also a stack
trace showing you where the exception was triggered
and all of the different functions and methods
that you were inside when the exception happened.
So when we're decoding JSON, we're going to need
to use a try, catch block, and that's the reason we'll need it,
is to sort of handle this kind of error in our decoding process.
So those are all the concepts we need to start loading data from an API.
So let's add that to our app.
So let's open up Android Studio again and jump back to our adapter.
Since our adapter was where all of our original data was stored,
we're just going to make some modifications here.
So this was our previous data store, just the static list.
So let's get rid of this and say that, instead of creating that list,
let's just create a new empty array list.
So now we're starting off with no data loaded
and we're going to load that data in from an API.
So first let's create a new method to load some data from that API.
So we're going to say public, our return type
is void because we're not returning anything--
we're just going to store it in that field--
and let's call this method loadPokemon.
So the first thing we'll need is the URL that we're looking to load.
So let's create a new string variable called URL.
And then if we jump back over to that API we can just copy and paste this.
So we know it's going to be PokéAPI API version two,
and then we can copy in the rest of the URL.
So that's the URL that we want to load our data from.
The next step is to write out a JSON request.
So this is where we're going to use that Volley library.
So inside of Volley there's this class called JSON object request,
and as you'd guess this is a special request that's
going to handle a JSON response.
So let's create a new JSON object request.
So the first parameter to this method is the type of request
that we'd like to make.
So to specify that, we're going to use the request class defined in Volley.
Then we're going to say, method and get.
So a get request is just a type of request,
much like your web browser might make, that's
just getting some data from a URL.
We're not sending anything.
We're just trying to get a list of Pokémon,
so this get request makes sense.
The next parameter to this method is the URL to request.
That's easy.
We just created URL.
This next parameter we're not going to use.
We're just going to pass null because we don't
need to send along any information.
And then lastly, we're going to define a method that's going
to be called when the request finishes.
So to do that, we're going to use a class also
from Volley called response.listener.
So we can say new response.listener, and again,
let the autocomplete do the work for you here.
So you'll notice what this did is it created an anonymous class
in that same syntax we used before.
So we're defining a new class that extends response.listener.
It looks like it's passing in a type of JSON object,
and that's being used here, because we know
we're going to get back a JSON object.
And then we're overriding this one method called onResponse.
And this is the method that's going to be called for you automatically when
the data finishes loading.
So now that we've defined this method, let's
start passing the response from the API.
If we jump back to the API page, let's remember what
their response is going to look like.
It looks like we have an object with four keys here,
but we really only care about this last one called results.
And it also looks like the type of this results is an array,
and it happens to have 151 items.
So we're going to start parsing this.
So we're going to say we want adjacent array, and we'll call this results.
And to access that key from that response object,
we can say response get JSON array results.
And so what this is going to do is it's going to look into that JSON,
look for a key called results, and then take that key
and make sure that it's an array.
But you can see here that I already have this red underline that
says unhandled exception.
And this is where our try catch block is going to come into play.
Cause in the event that there's no key called results or that key
isn't actually an array, Java is going to throw an exception and say,
this data doesn't look like what you thought it looked like
and you need to handle that.
So let's handle that.
Let's put that inside of a try and now we can
catch the exception that it's throwing.
So if I just hover over this it says, unhandled exception, JSON exception.
So let's just say JSON exception.
And now at this point in a real app you'd
want to somehow display a message to the user saying, you know,
that some error occurred or try reloading.
But for now let's just handle this ourselves and print it out to that log.
So we can use that log class again, and this time say log.e, e for error.
And we can say tag is just CS50, and we can say some JSON error.
Then we can also pass along that exception object,
and that way it can get logged.
So let's quickly just do a make here.
And it looks like I forgot a semicolon, so let's add that.
And now there's one last parameter that we have to add to our JSON requests,
and it's also going to be this anonymous class,
but it's going to be an error listener.
And so this is going to get called if something
happens like this URL doesn't exist, and we won't worry about this too much.
For now we can just sort of do another log.e, say CS50.
We can say, Pokémon list error.
Something simple like that.
Now let's make sure our app compiles, and it does.
So now let's get back to passing that JSON.
So now we have this object called results of type JSON array.
And so the next thing we want to do is iterate through this array
and add each of those results to our list.
So to do that, we're going to write a for loop
to iterate through all of those elements,
and we'll just write a traditional for loop here.
So we'll say int i equals zero, i is less than results,
and this object has this method called length that's defined on it.
And we'll say i plus plus.
And so now to access each individual element
we can say there's some object that's a JSON object that
represents a single result, and that's going to be results.GetJSONObject of i.
And so now each of these objects represents
a single one of these elements.
So this object should have two keys, one called name and one called URL.
So if now we want to create these Pokémon objects,
we want to make sure there's a place for both the name and the URL.
And if you remember what our model looks like, it looks a little different.
We have name and number.
So let's just quickly update this to have a URL.
So we'll change the field name, we'll change the parameter,
and lastly we'll change our getter to be getURL and return URL.
So now we have a place to store both of those things.
So now to actually store them.
We're going to say Pokémon.add, and now we can create a new Pokémon object.
Remember, that constructor takes two things.
It takes a name, and so that's going to be result.getString,
and that was called name.
Just space this out.
And the other argument is result.getString,
and we called this URL.
Now we'll add a semicolon.
And let's make sure our app compiles.
Looks like we're missing a--
oh, had an extra semicolon there.
And now remember we sort of changed that Pokémon object,
so we got rid of this getNumber method, so let's just comment this out for now,
and we'll come back to this later.
So there's one there.
And it looks like that's the only usage of it.
OK, so now we should have a list of Pokémon objects,
but this time loaded from an API rather than that static list.
So the last thing we need to do here is to actually kick off
this request so it actually starts running.
So to do that, we're going to use an object called a request queue.
So this is from the Volley documentation.
You can read about that.
But we're going to create an object of type request queue,
and we'll just call it request queue.
And if you look in the documentation, this object
requires a parameter that's called a context.
And right now our Pokédex adapter doesn't have access to that context,
but our Pokémon activity does.
There's a method on activities that's called getApplicationContext.
And so we want to do is pass that object from our activity to the adapter.
And that's pretty easy to do.
Let's just create a constructor.
So we'll say our Pokédex adapter constructor takes one thing,
it takes a context, and we'll just call it context.
And now with that context we can create our request queue.
So we can say request queue equals Volley.newRequestQueue,
and this is where we're going to pass in that context variable.
Inside of this constructor is where we can add our API requests.
So let's just say loadPokémon right there.
Lastly, with this request queue, we want to make sure
that our request is actually added to it.
So when I say requestQueue.add request, this
is what's actually going to kick off the request and actually make the request.
In this first line here, all I've done is
define an object that represents a request,
but I haven't actually used it yet.
So to actually use it, all I have to say is I'm going to add it to this queue,
and this queue is going to actually make that request to pokeapi.co.
So we changed the constructor for our Pokédex adapter,
so now we also need to change our main activity.
So here is where we created the Pokédex adapter,
and so you notice now that we have a syntax error, which makes sense,
because we asked the constructor to take one parameter.
So we're just going to call that getApplicationContext.
This is just a method that's defined on activity.
So we're just going to call this and now we can pass along that field
that our request library needs.
So let's give this a shot.
Let's make sure that this compiles.
Looks like it does.
So let's now try running this inside of the simulator.
Looks like we installed successfully, so let's open it up.
Hmm.
Looks like nothing's there.
So our API requests went through and we loaded all of this data,
but now we need to notify our Recycler View that our data actually changed.
All we've done is just change this underlying model,
but we need to tell the Recycler View, hey, go ahead and reload that data
so you can display the latest stuff.
And that's pretty easy to do.
The only thing we need to do is call this method notifyDataSetChanged.
And this is a method that's defined on the RecyclerView.Adapter class.
We didn't have to write this.
Because we're extending that, we can just call that method.
So now that I've added that notifyDataSetChanged method,
let's try again.
Looks like we installed successfully.
We open the app--
still blank.
So let's take a look at what's output in our log.
So let's come down here to the logcat, open that up,
and if we scroll up a bit, looks like this red doesn't look good.
So here's that Pokemon list error, which means that this on error response
is being triggered.
It says that I got permission denied.
I'm missing the Internet permission.
So if you remember back to when we're looking at that Android manifest XML
file, we saw there that that's where we listed a few things about what
our application is allowed to do.
Because by default Android apps are basically in a sandbox
where they only have certain permissions.
And one of the permissions that we need to add
is the ability to communicate to the internet.
That's not something that's added by default.
So we have to go into our Android manifest and add that ourselves.
To do that, we're going to open back up that Android
manifest and, underneath this application tag, we're going to add
uses permission.
Again, autocomplete is right there for me.
It says internet is probably something you want to add.
So we're just going to let that autocomplete and we're good to go.
So this is a new XML element.
Says I want to use a permission, that permission
is this special Android permission that lets you communicate with the internet.
So now our app is allowed to do that.
So let's run it one last time.
Great.
This time we have no errors in our logs, and in fact we've
loaded up all of this data from the API.
So we can see that we're using this API response now in our app,
and our Recycler View is basically displaying data that we
got from somewhere on the internet.
If you remember before, when we hardcoded those values,
we just sort of uppercased the first letter ourselves,
because we were typing it in ourselves.
And it looks like this API doesn't do that,
but it looks a lot nicer when it's capitalized.
So let's write that capitalization ourself.
Let's jump back to the Pokédex adapter.
And now rather than just using whatever field is returned under that name key,
let's do something with that before we create the Pokémon object.
So let's create a variable name, and that's
going to be equal to result.getStringName.
And so now let's capitalize the first letter.
To do that, I want to take the first item in this string, the first letter
in this string, capitalize that, and then
append onto it the rest of the string.
So luckily, Java strings have a method called
substring that you can use to get sort of a string inside of that string.
So here's what I mean.
If I say name.Substring, you can see here
the autocomplete tells me that it takes two arguments, a begin and an end.
So to get just the first letter, I want to take a begin index of zero
and an end index of one.
That's going to get me the first letter.
And with that first letter, I want to uppercase it.
So I can say, toUppercase.
And then I just want the rest of the string
as it was, because that's already lowercase,
so let's just say name dot substring.
Now if I just give this one argument, what it's going to do
is take the rest of the substring.
So I don't need to specify an end index, it'll
just keep going until it hits the end of the string.
So now let's run this.
We open up the emulator.
That's what we wanted.
Now we have everything capitalized and looking like it did before.
So now that we've loaded the list of Pokémon from this API,
let's also load in the data about a single pokemon.
To do that, let's first jump back to the API
to remind ourselves what that data looks like.
So we can use this URL parameter to say what request we need to make to get
information about each Pokémon.
So if we take the first one, I can get rid of this limit to say /1,
since that's what this URL is telling me to get, and click Submit.
And so this is the resource that we're going to get back.
It has a bunch of different information about the Pokémon.
And so let's say that in our app we want to display the type of each pokemon.
So each Pokémon can have one or two types associated with it,
and then it's given to us in the API with this types field.
So what we want to do is, in our second activity, once that's loaded,
we want to load up this information and then use the types field
and display that via some other text views in the app.
So just as we did last time, let's first change the view
and add the elements we want to add, see if we get any changes to our model,
and then finally hook up the controller.
So let's open up that activity Pokemon XML file, where we have those text
views, and let's add a few more.
Let's just copy here.
So we want one to be called our Pokémon type one,
and the other we'll call Pokémon type two.
Feel free to adjust the text size or padding.
I'm just going to leave them alone for now.
So next, let's open up our Pokémon activity.
So remember last time that we passed along the name and the number.
This time we don't need to do that.
What we actually want to pass along is that URL,
since we can use that URL to load in information about a Pokémon.
So rather than getting name here, let's get URL and call this parameter URL.
Now we can delete everything that's referencing number here
and we can delete this as well.
And finally, let's move this URL just to a field so we can use it later.
And there we go.
So all we're doing is saving some URL that's
passed into us from the internet.
OK, so just as we did last time, let's make a new method to load in some data
from the API.
So let's call this public void, let's just call it load.
And then rather than writing all of that stuff by hand again,
let's just come back to our adapter and just copy what we had before.
And when we paste this code you'll notice that Android Studio is telling
us it looks like there's a bunch of objects you're using here
that you haven't imported yet.
And if I just click OK, it's going to import them all from me
automatically, which is pretty nice.
So the first thing we want to change is this URL.
We don't need to use this anymore.
In fact, we've already saved the URL we want to call, so let's get rid of that.
Next, our results field is going to be a bit different.
So let's just get rid of this.
Finally, let's change our error messages so they're a bit easier to read.
So this is, let's say, a Pokémon JSON error,
and this let's call it a Pokémon details error.
So that way we just know what's being triggered here.
And now let's parse this new JSON response.
So if we come back to that API, let's remember what we're looking for.
The first thing we're looking for are types.
So it looks like this types field has a list of objects,
so we can say response.getJSONArray, and that's types.
And so let's just assign that to a JSON array called type entries.
So just like we did before, let's iterate through this.
Let's say for i is zero, is is less than type entries.length, i plus plus.
So let's remind ourselves what each entry in this list looks like.
It looks like it's an array of JSON objects, and each of those objects
has two keys, one called slot and one called type.
So let's get our slot first.
So we want to say JSONObject, we'll call this a type entry,
is type entries.JSONObject, and we'll pass along the index to get.
Now we can have a slot integer that's just type entry.getInt slot.
So that's reading the first value from that API.
So now let's read the second.
To do that, let's create a string.
We'll call it type.
And now we'll say type entry.getJSONObject.
We remember that was called type.
And we only want the name field here, so we can just say getString name.
Let's double check that that's right.
We have a type and a name, so that looks good.
So now that we have these two variables in memory,
let's use them to display something in the UI.
So the first thing I want to do is, just like I
did before I had these text view fields, let's add those other two text view
fields that we just created.
So one was called type one text view, and the other was called type two text
view.
Just as we did up here, let's use findViewByID.
So we'll say type one text view is findViewByID type one,
and our type two text view is findViewByID type two.
Now we can use these two fields just as we did before.
We can say, if we're in the first slot, then use type one text view, set text,
and we'll just use that type variable.
And if we're in slot two, we'll use the other text view.
OK.
And something else I like to do is just, to make sure
that those fields are empty, we can up here just say,
type one text view, let's just set that to the empty string
and set this to the empty string.
Sometimes when I'm making views I like to use placeholder values
just so I can get a sense for what the view will look like with real data.
So this just makes sure that those are gone.
And let's make sure this is type two.
OK, so that's the types.
So let's add back in the name and the number since we got rid of those.
Let's come back up here and do that.
So let's jump back to our response just to remind ourselves
what this looks like.
So it looks like there's two keys we can use here.
One is called name.
That's just the name of the Pokémon.
And the other is ID, and that's the number.
So we're going to have the same code we had before, just using this API data.
So we'll say my name text view, let's set the text to be response.getString.
It's called name.
And our number text view, same thing before.
We're going to set text, we're going to use string.format.
Our format string is going to be 03d, meeting we want a three digit number.
We're going to pass in response.getInt, and that was called ID.
Now if we save it, let's compile, see if we have any errors.
Doesn't look like we do.
That's great.
So the last thing we need to do is to pass in this URL to this activity.
This was just this kind of second half.
Let's go back to that first half.
So let's come back to our Pokédex adapter and look at that click handler.
So OK, looks like before our click handler was passing in an extra, one
called name, one called number.
Let's now pass in an extra called URL.
And remember that our Pokémon object has this method getURL,
and that's going to get that URL as a string.
Now we only need to pass that in, because we're
no longer using the name and the number from that object.
We just care about that URL.
So let's run this and see what happens.
Looks like everything installed successfully.
Here's our list just like we expected from last time,
and let's try tapping on a Pokémon.
Looks like nothing happened.
Let's think about why.
If we jump back to our Pokémon activity, it looks like we created this request,
but Android Studio has this sort of underlined and gray and it says
it's never used.
And remember, last time before we could actually make the request
we needed to add it to that request queue.
So let's create another request queue just like we did last time.
Say requestQueue.
And then we can create one using that same line as last time.
We can say, my requestQueue is equal to Volley.newRequestQueue.
Again, we just need to pass in that application context.
Because we're in an activity, we already have access to it.
So that's going to create the request queue.
And now let's use it.
Let's say requestQueue.addRequest.
And notice that same gray underline is also on this load method,
because that's never used.
So let's just call that method immediately
when the activity is created.
So after we've wired up all of our views, we've created the queue,
now let's call this load method so we actually make that API request.
Now let's try running it again.
OK, looks like we're running.
Let's try again.
There we go.
So we can see here that the activity was blank for a second
just as it was loading in the data.
But now here's that data that came in from the API.
We have the name, the number, and both of the types.
And you can try any of the Pokémon.
You can type in anything and we're going to get exactly the response
from that API.
So that's how you can use Java to load information from an API
and bring it into your app.