Alright so at the end of the last
video, well actually the one before, not
including the build update files, well
actually the end of the video prior to
that, because of course the last video I
talked, I showed you how to actually update the
Kotlin Runtime. But in that previous
video we talked about, we were going to
add the getId function to our Tasks
Contract class, and obviously you can see
here we've got an error there because
that function doesn't exist at the
moment. Now there's a few other things we
need to do in there, so I'm going to do
them all together. Now that we understand
how URIs will be used, this should
make more sense for you. So I'm going to
start by opening up my TasksContract
class, and you want to make sure that
your cursor, when we start typing, is
above the object Columns. Don't be typing
in there because you'll get a weird error
if you do that. So I'm going to put a
comment here about what we're doing.
We're creating a public constant here, of
CONTENT_URI so that
other classes will be able to use this
URI for our Tasks table. So this is
going to be "The URI to access the
Tasks table". And in terms of the, what I'm
typing here, we're going to type val and it's
going to be CONTENT_URI: Uri
is equal to Uri.with
AppendedPath, in parentheses - we're going to
ignore that error that appeared up
before there with the IDE in the bottom
right hand corner - CONTENT_
AUTHORITY_URI. There should have been
a comma there, so TABLE_NAME.
Okay, so the CONTENT__AUTHORITY
_URI, that's coming
from the AppProvider file. Now once
again we didn't have to concatenate
strings to create the CONTENT__URI from
the CONTENT__AUTHORITY and our TABLE_NAME.
The Uri class provides several
methods for dealing with URIs. In
here we're creating a new URI with a
path appended, using the withAppended
Path method from the Uri class. Now
CONTENT_URI will be used
by external classes, including different
apps that use our content provider to
refer to our Tasks table. Alright, so the
other thing we decided to put in this
class
is the getId method. Now that will be
used to extract the ID from a URI, and it
also makes sense to include the
complimentary method, buildUriFrom
Id, to do the opposite.
It'll add an ID to a CONTENT_
URI. So I'm going to add these
functions we're about to write, below the
definitions for the Columns object at
the end of the class. So I'll start by fun get
Id parentheses uri :
Uri. Then a right parentheses
then a : Long, and we'll add our left
and right curly braces, and we want a
return ContentUris.parseId, in
parentheses uri, which of course was
the argument passed in this method. Then
the second one was that other
complimentary method I talked about: fun
buildUriFromId. The argument for
that one is an ID of type long, id :
Long, and that returns a Uri. So
: Uri left and right curly braces, and
we're going to return ContentUris
dot withAppendedId, in lower case with,
and in parentheses it's going to be
CONTENT_URI comma id.
So I've done something slightly
different with the buildUriFrom
Id function. Now I could have used the
withAppendedPath function of the Uri
class, like we did to create the CONTENT
_URI, but that would
involve converting the TaskId, which is
a Long, into a string. Now the function
call would also be slightly confusing
because we're not actually appending a
path in that case. The Uri class
doesn't have a function to
append an ID and there's no reason why it
should. IDs mean something in our
content scheme but aren't part of the
basic URI specification. Now you've
probably already called up the
documentation for the withAppendedId
function, but if you haven't, open that
now and take a quick look. You'll see that
it says it appends the given ID to the
end of the path, so that's pretty
straightforward. The documentation for
the ContentUris class also is
really interesting if we have a quick
look at that,
because it reviews what we've already
discussed about Uris and applies it
to the CONTENT_URIs that we're
using with our content provider. Al
right. So the getId function performs
the opposite function, so it returns the
ID from a URI using the parseId function
of CONTENT_URIs to do the job. Al
right so there's just two more constants
now we need to add, to complete our Tasks
contract class. So I'm going to go up to the
top and add those just after the CONTENT
_URI. So the first one,
we'll do const Val and the CONTENT
_TYPE is equal to, in double
quotes, vnd.android.cursor dot
dir, then a slash, a forward slash, vnd dot then a
dollar sign CONTENT_
AUTHORITY. So that's CONTENT__AUTHORITY dot
then a dollar sign TABLE_NAME.
And there shouldn't be a dot there,
after the dir there should be slash
actually. So it's vnd.android.cursor dot
dir, slash, a forward slash, vnd dot then the dollar sign
CONTENT__AUTHORITY, then a dot
then a dollar TABLE_NAME. Now we're
actually getting an error here, and it looks
like I might have made a mistake with
the CONTENT__AUTHORITY definition. So
let's just have a quick look at that -
we'll go to the definition for that. And in
fact I did make a an error there,
so I've made it a private and it
should have actually been a const. So I'm
going to delete the word private there
and make that a const, and if we go back
to our TasksContract, that error then
disappears. Alright let's add the second
one now. So it's const val, this is
CONTENT_ITEM_
TYPE. Set that equal vnd dot
android.cursor dot, this time it's dot item
instead of dot dir, then /vnd dot, a dollar
sign CONTENT__AUTHORITY, noticing that
that is now coming up correctly because
I've corrected the definition there. So
CONTENT__AUTHORITY dot and a dollar
TABLE_NAME. Now I'll come back
to these constants shortly, but
before we switched to the TasksContract
class to finish it off, we were looking
at the query method in AppProvider.
So let's just go back to AppProvider again,
noting that we've no longer got an error
on line 78. Now if the URI doesn't have
an ID the matcher will return the value
Tasks - line 74, effectively, is where it's
testing for that - and we just call in the
queryBuilder setTables method here, to
add our Tasks table, or we're actually
setting the property. Now I'm
referring to the Java method
here, and that's because queryBuilder's
written in Java. Because we're
programming in Kotlin we can assign the
value directly to the tables property,
which is effectively what we're doing -
again on line 74. Now if there is an ID,
the matcher returns TASKS_ID,
and that's the code for that is line 76,
starts on line 76, and we're using the
appendWhereEscapeString method to add
a where clause to specify the ID. Now the
same thing will ultimately happen for
the other tables once we've written
their Contract classes and uncommented
this code. Now if the URI isn't
matched, down the bottom here, we'll throw
an illegal argument exception and the
rest of the code won't be executed. But
assuming the URI is valid and matches
one of our values, we can use the App
Database instance to get a readable
database object. Now we're only querying
the database so we don't need to call
the getWritableDatabase, so I'm using
this, referring to this code here now, on
line 101. So we don't need to call get
WritableDatabase because it's a query,
but we'll be doing that shortly when we
come to insert rows into the database.
But at this point we've now got
everything we need to finish building
the query to retrieve the data. And now
it's time to use some of these those
other parameters that the query method
defines, so we'll just go back up and we'll
look at that again. You can see line 66
there, we've got quite a few arguments
showing there, and what I might do is just going to
press ENTER there, just so we can more
clearly see what those arguments are.
Alright, so looking at them, we'll start out
with the first one, and we'll take the
opportunity to open up the
documentation for the query method. Let's
do that. Alright that'll do, we can see, I'll put this down here
a little bit further so you can still
see the original parameters because we can't
quite see them onto the right of the screen
there. But in terms of the actual
arguments, the first parameter's a URI
and we've discussed that already. The
next one is a projection. Well what is
projection? And what that is is a
string array
containing the names of the columns that
we want to be returned by the query. So
this method effectively provides a great
deal of flexibility in specifying what
we'll get back from the database. Now the
final parameter is a sortOrder. It's
just the name of a row, or several rows,
separated by commas that the data will
be returned sorted by. so whatever's
passed for the sortOrder will
effectively appear in the SQL ORDER BY
clause. Now I've deliberately skipped
over the selection and selectionArgs
parameters. I'll come back to them, as
it's much easier to see how they work with
an example, rather than just talking
about them. But outside the when, our
content providers query method calls the
queryBuilders query method, and you can see
that here on line 103. So that's the one
we're writing, not the queryBuilders
query method. So it's passing it the
database object that we get by calling
the AppDatabase getInstance function.
You can see that db being called there,
being passed I should say, to the query
Builder.query function, or method,
because again the query builder is
written in Java. Now most of the
parameters of our query method are
passed straight into the queryBuilders
query method, but it also includes two
other parameters that we've just passed
null for. And you can see they're grouped
by, and having, both null currently. Now if we
check out the documentation this time
for the queryBuilder.query method, you
can see that looks very similar to our
content providers query method. So one's
your projection to specify which columns
should appear in the result, the
selection and selectionArgs
parameters that we'll look at shortly,
and a sortOrder. But it also allows
groupBy and having clauses to be used.
Now we looked at groupBy in a SQL
tutorial earlier in a previous section
in this course. having is like a where
clause but it also applies to the
grouping, so I'm not going to use having.
But if you did something like, for
example, group by month to get the total
of some value for each month, then you
could do something like; adding having
days equals 31, to only total the data
for months that have got 31 days. So it's
useful to know how you can do that, and
there's quite a lot more to SQL than
we've covered in the tutorials,
but we won't be using the having clause in
this app. So go back to our code now and
the function finally finishes - down here
is the last line returning a cursor. So
it's returning the cursor that it's
got back from the queryBuilder dot
query method. Now to make it obvious that
we get a cursor, I've used two lines of
code and also logged the number of rows
in the cursor. When we're happy that it
works we can directly return the result
of calling queryBuilder.query,
instead of storing the cursor in a local
variable first. Okay, and we haven't
created a user interface yet, but we can
test the content provider by logging the
values in the logcat. But there's
something else we need to do first, and
that's to register our content provider
so that the content resolver knows
about it. So we'll do that and then test
the content provider in the next video.