Alright, so we got the layout that our
custom adapters going to use, so it's now
time to create the adapter. So you want
to open the project pane if it's closed,
and right-click on the package and choose
New Kotlin File/Class. We're going to
call this one FeedAdapter, and I suggest
you change the Kind to Class, just to
save us a little bit of typing. Click OK.
Now, we want to extend the ArrayAdapter
class, and that's so that we'll have all
the functionality of an ArrayAdapter,
but we can then change some methods to
deal with the more complicated view that
we want to use. So to make our class
extend the ArrayAdapter, we use the, we
just declare it to be of type Array
Adapter, using the normal Kotlin syntax.
So it's colon after FeedAdapter, then it's
ArrayAdapter, diamond operator, and it's
going to be Feedentry, and that brings up an
error, and the error here is saying This
type has a constructor, and thus must be
initialized here. So basically, it's
telling us that we need to have a
constructor. Let's just have a look at
that link that I posted earlier. Open a
browser and paste the link in. Now we're
interested in the public constructors
which is in this summary section here,
and you can see that there's six
constructors. So how do we know which one
we want to use? Well it turns out our
data is being stored in an ArrayList of
FeedEntry objects, so the one that we
want is this one here, the fifth one. But
if that's not obvious, we're going to
ignore it for now and see what happens.
As you can see, though, all six
constructors need at least a Context and
a resource. Now the resource is the
layout that we'll be using - our list
_record.xml layout. So if
all six constructors need at least two
objects, or those two objects, we'll just
include them for now, and then we'll pass
them to our adapter in its Primary
Constructor. So let's go back - go ahead
and add a constructor. Alright, so what
we need to do then is, come over here to
class FeedAdapter and in parentheses,
context colon space Context,
and then we want to do val resource colon
Int, and end of parentheses. Then still leave
the colon and then the ArrayAdapter Feed
Entry there. Now this, actually, still
gives us an error that the type has a
constructor and thus must be
initialized here. So we're going to pass
the two arguments that we get into our
Primary Constructor, and we do that by adding
parentheses on the end; context, resource.
And that keeps things happy. Now we're
going to add some logging later, to show
what's happening, so let's add a tag for
that. That's going to be private
val TAG is equal to FeedAdaptor. Now the
other thing we're going to have to do is
inflate the xml resource to create the
View. Now inflating an xml resource is
just the term used to describe taking
the xml representation, and producing the
actual widgets from it. So the xml in our
list_record.xml file
describes the TextView widgets and their
constraints, font sizes etc. So a Layout
Inflator takes that xml, and inflates it
to produce the View object that can be
displayed on the screen. Now we get a
LayoutInflater from a Context, and we've
been given a context in the constructor.
Now rather than retrieve the inflater
each time we need it, we'll store it so
that we can use it wherever we need it.
So let's go ahead and do that with
private val inflater equals, and it's
going to be LayoutInflater.from
parentheses context. Now there's that
Context again. The Contexts are used
extensively throughout the Android
framework, especially when dealing with
the user interface. Now we saw a context
a couple of videos ago, but what is a
context? So let's just start by having a
look and seeing what the Google
documentation has to say about it.
Paste in a link.
Now there's a couple of important things
on this page. The first paragraph, as you can
see here, explains that the context is an
interface to global information about an
application environment, and that it
allows access to application specific
resources and classes. So it, basically,
holds the state of the application or
activity while it's running. Now imagine
you're the manager of a multi-screen
cinema complex. When you decide to show a
particular film, you need to know which
screen to project it onto, so the screen
forms part of the film's context. You
also need to know what time to show it,
and also how much to charge for the
tickets. So again, these are part of the
context for that particular screening of
that particular film. So without access
to the context, you wouldn't know which
room or screen to show it in, or what
time to show it. So a Context in Android
serves a similar purpose. So it contains
information on the things about an
Application or an Activity, that the
system needs in order to manage it. And
it also provides access to the various
classes that are needed, such as the Layout
Inflater, that we'll be using.
So let's actually have a quick look at
the documentation for the LayoutInflater
class. So a LayoutInflater
instantiates a layout XML file into
its corresponding View objects, which is
really just a fancy way of saying that
it creates all the View objects that's
described in the xml. Now the
documentation also states that you
shouldn't create your own instance of a
LayoutInflater. Instead, you should use
getLayoutInflater or getSystem
Service to get a standard Layout
Inflater that's already hooked up to the
current context, and correctly configured
for the device you're running on. Now if
we run our app on a tablet, it may return
a different LayoutInflater than if we
ran it on a phone or an emulator - we
don't care. As long as we ask the context
to provide us with an inflater,
everything works fine. Now the important
thing is that we need a context which we
can, you know, request things like the
layout inflater from. Now I've used the
static
LayoutInflater.from method - if we just go
back and have a look at that - here on
line 13. We use that particular
function to get our LayoutInflater and
then we pass the context as a
parameter. So that just wraps a call to
getSystemService with the correct
parameters, and there's no difference
between any of those three ways to get
an inflater from a context. Now switching
back to the documentation really briefly,
for Context again, another interesting
thing is the list of Known Subclasses.
Because Activity and AppCompatActivity
both extend the basic Context class, we
can use the current instance of an
activity, whenever we need to pass a
context to our function. So you'll often
see this or MainActivity.this used,
when calling a method that needs a
context. So you can see here like
Activity and AppCompatActivity are
both subclasses. Alright, so moving on.
We'll go back to the Android Studio code
again. So we've got a class that extends
the ArrayAdapter. Now to make it work,
we just need to override two functions
of the base class.
Now when the ListView Scrolls items off
the screen, it'll ask its adapter for a
new view to display, and it does this by
calling the getView method. So that's
the first method that we need to
override. Now the ListView also needs to
know how many items there are, so it
knows things like how to represent the
scroll bar, to indicate how far through
the list that you've scrolled. Now
to get the number of items, it uses the
getCount method. Now there are
other methods that we can override if
we need to further customise the adapter,
but these two are all we actually need
at this stage - but these two are all that
are actually required. So I'm going to
get Android Studio to override those,
to generate the basic functions for us.
So it's basically the getView and get
Count. So I'm going to select both of
those and click on OK,
and you can see they've both been overridden
on the screen now. So starting off, the
getCount method's going to be pretty
easy because we just need to return the
number of items in our list. So we come
back to that - to getCount - and we do a
return applications.size. Now that
would work, except we don't have a list.
Now we obviously need one, as it's our
list of FeedEntry objects that'll
provide the data to display. So going
back to the documentation on the Array
Adapter, let's have another look at these
constructors. So the last four
constructors all show a final
parameter that's either an Array or a
List. You can see these ones here - the T
left and right square brackets are objects,
or a list, as you can see there. It's
either an Array or a List. Now we're
using an ArrayList, which is a specific
implementation of list, so the
constructor we're interested in here, is
really this fifth one. So therefore, we
need to modify our class Primary
Constructor, so we can pass in the
ArrayList that's going to contain our
data. So let's go back to a Android Studio
and make a change here. So at the moment,
we've got class FeedAdapter context
Context, val resource Int. So let's go
ahead and change that. So we're going to
use the next one, and we're gonna add
another argument which is going to be
comma val applications : Then it's going
to be List diamond operator FeedEntry -
list of FeedEntry objects - and you saw
that the error then disappears on line
18, now that we've correctly
specified that. Now if you're not sure
which constructor you need in situations
like this, start off with the minimum.
And as you continue writing the code,
you'll spot where you need to use a
different one, just like we did here, and
then you add that, and so on and so forth.
Okay, now if you don't override the get
Count method, the ListView won't display
any records. It needs to know how many
items there are so that it can create
the list of rows. So if you create an
adapter and just get a blank ListView,
check to make sure that you've provided
the correct count of the items in the
getCount method, and you can see that
we've done that here, on line 18. Al
right, so the getView
method's called by the ListView, every
time it wants another item to display. So
let's go through a very naive
implementation to get started, so we've
got something to work on.
So I'm gonna get rid of the super.get
View. We could do something like val view
equals inflater.inflate resource
parent false, and we'll just go through
this code first and then talk about it.
So val tvName : TextView is equal to view
dot findViewById R.id.tv
Name, and we can just take a copy of that
and do that for the other two fields, so
we also got tvArtist and we've also got
tvSummary.
Artist, and we could do something like val
currentApp equals applications position.
Then we could do tvName.text equals
currentApp.name - not sure whether copying
pasting is any quicker than just typing
it - but Artist, Summary, summary, artist,
then a return view, return view. So that's a very
naive implementation, and it's also not
very efficient, because we're creating a
new view every time and having to call
findViewById for each of the TextView
widgets. Now we're going to modify this
soon, but for now, we'll just get it
working. Now notice that Android Studio
is suggesting, over here, that we should
use the View Holder pattern. So that's
what we're going to do later. Alright,
so what have we done here, though. Well the
function starts off by creating a View -
we're doing that on line 23. It does that
by inflating the layout resource, and it
uses the layoutInflater that we
created from the Context in the
constructor. So it then finds the three Text
View widgets using the findViewById -
lines 25 through 27.
Now when the ListView calls this getView
method, it tells it the position of the
item it needs to display in the position
parameter - and that's this parameter here,
Int, that's been passed to this function.
So we retrieve the object at that
position
in our applications list, down here on
line 29. Then we set the name and the
artist and the summaries on line 31 through
33, into the corresponding TextView. So
once that's done, we're just returning
the view on line 35, and that's actually
our adapter finished. Now there are a
couple of things that we should change,
and we can see that Android Studio has
flagged up some
warnings on the right hand side, and I
showed one of those on the screen.
Now strangely, this version doesn't put a
tick in the margin for our
applications parameter. And you can see
here that it does underline - you can see a
little squiggle there - Property
applications can be private.
Now the resource can be private as well,
so we're gonna change both of those, and
that's going to make the line a little
bit longer, so I'm going to split it up. So
let's go ahead and do that, so we've got
the Context, val. I'm going to change it
before the val for the resource - I'm
going to put private, and also put a
private for the application as well,
applications object. And let's put that
on the next line, then I'm going to press
ENTER there so we can see what we're
doing a bit better there, and put another
space there. The next warning we've got
over here, is the fact that, or the class
FeedAdapter's never used, but also the
property TAG is never used. Well there's
no logging at the moment - logging when
these two functions are called will make
it much easier to see what's going on,
and so therefore, let's do that. So let's
actually add some code for that, so
firstly, for the getCount, we'll put a Log
d -or Log.d, I should say - and it's gonna
be TAG, and we'll put double quotes get
Count called. And we'll also do the same
for our getView, and I'll do that
as the first line as well. And instead of
getCount, obviously, it's gonna be getView
called, and that obviously gets rid of
one of the warnings now because TAG is
being used. But that'll be enough to get it
working now - we can look at some of
those other warnings later. Let's
actually see if our adapter works. So all
we should have to do is change the
adapter that we create in the onPost
method of the DownloadData class, to use
this new one. So let's go ahead
and do that. So back in MainActivity, in
our DownloadData onPostExecute,
we've got val parseApplications. Then
we've got our, moving on down, we've got
our arrayAdapter code here. So what I'm
going to do is comment out these two
lines, because that's our old
adapter, and we're going to use a new one. So
I'm going to type val feedAdapter
is equal to FeedAdapter, and we need to
pass some arguments. The first one's
going to be propContext. Then we need
R.layout dot - this time it's list underscore
record, and then we need parse
Applications.applications, which is
our list of FeedEntry objects. Then we
need propListView.adapter
equals feedAdapter, this time. Alright,
so let's actually try running that to
see that it works.
Alright, so there's our app running and
you can see that it's looking a lot more
impressive now, than what it did look
like before, and obviously, we can scroll
that up and down. But what I'll do, this
video's got quite long. So I'm going to
stop the video here, and in the next one,
what we'll do is we'll look at making
our FeedAdapter more efficient. See you
in the next video.