Alright so let's move on now to the
insert function. So we're going to start
off with the same code that we've used
in the two other functions. We're going to
check the URI and get a result back from
the uriMatcher. So what I'm going to
do is just take a copy of the code that
we've used in the query function, and
come down and we're going to start by
pasting that over the TODO in our
insert function. Now the code that uses
this function will often need to know
the ID of the new row, so this function
returns a URI that includes the ID. Now
the function accepts a URI without an ID
and returns the same URI with an ID
added. So let's start coding this, and one
thing we are going to do slightly
differently to the query method, is get a
writable database object in each of
these cases. So let's start by typing
some code. So val recordId : Long and
then val returnUri :
Uri. Alright, so we now need to add a
block here, a when match block, so I'm
going to type when and parentheses match,
then left and right curly braces. We want
to go through and check firstly for TASKS.
So TASKS, add the arrow as we normally do
for a when, and then in there we're going
to type val db equals AppDatabase dot
getInstance, in parentheses context, and
dot writableDatabase. I'm going to do
exactly the same thing and take a copy
OF those three lines for the TIMINGS, for
TIMINGS as well. Alright. So in the query
method we've got a readableDatabase
from our AppDatabase. Here, for obvious
reasons, because we're inserting data,
inserting new rows, we're getting a
writableDatabase instead. Now the get
ReadableDatabase and getWritable
Database functions can be quite slow, so
we want to avoid calling them if an
invalid URI's used. Now we do that in
the query function by not calling get
ReadableData
base until after the switch. If the URI
was invalid, an exception is thrown and
the final bit of code isn't executed. In
the insert method we need to use the db
in each of our cases. Now it might look more
natural to get the writable
Database before the when, but that means
we'll be calling getWritableDatabase
before we've checked that the URI is
valid. The other thing to note here, is
that we're not using the TASK_
DURATIONS URI values. The database
view is only used for querying the
database, not for writing to it. That
means we don't need to include it in the
insert, update or delete functions. Now as
well as the URI, this function will
receive a ContentValues object holding
the values to insert. We'll look at
ContentValues later. Firstly, what I'm
going to do is add an else here and then I'm
going to add the code for inserting into
the two tables. So for our when, else, in
that case with the arrow we're
going to throw an IllegalArgument
Exception, parentheses, we're going to put
Unknown uri colon then a dollar
sign uri, just so we've got something
to show on the screen.
And in terms of writing the database, or
writing to the database, we're going to
put recordId is equal to db.insert,
and in parentheses it's going to be the
name of our table. In this case it's Tasks
Contract.TABLE_NAME comma
null comma values. And for the TIMINGS what
we're going to do is something very
similar, recordId equals db.insert.
This time it'll be the TimingsContract
dot TABLE_NAME comma null comma values. So
the insertion's done by the db's insert
function. Now we give it the name of the
table to insert into and the list of
values to insert. So whatever the Content
Values object is we're just passing that
on to the database insert method. So if
you have a quick look at the Content
Values up here in the parameters, you can
see here that it says "This class is used to
store a set of values
that the ContentResolver can process".
It's very similar to a bundle, in fact,
and we'll look at this in more detail
when we come to create a ContentValues
object, which we'll do when we use this
insert function. So close that down, and
remember that we changed the function
signature earlier. There's no point
trying to insert no values into the
table, so we made the ContentValues
parameter a non null type by removing
the question mark. Now the second
parameter here, which we're passing null
to - you can see here the name is null
ColumnHack - that's an interesting name,
and if we have a look at the insert
statement here. So open the documentation
for that - it talks about what that is. So
basically, if every column except the
underscore ID column of course, can
accept null values, then the values
parameter might contain nothing but
nulls.
Now SQLite doesn't allow the SQL
insert statement that will result from
that. There'd be no column names
specified and a statement wouldn't be
valid SQL. So in that case you'd
specify a column for this second
parameter, just to make the SQL
statement valid. But there's not a lot of
point really inserting a completely null
record most of the time, but it is
possible, and if you needed to do it for
some reason then you just specify one of
your table columns for that argument. Now
our tables won't accept a completely null
record. The names column is set to be not
null, so therefore we don't need to worry
about this parameter and we can safely
ignore it by passing null.
Now if the insert works the db insert
function will return the ID of the new
row. So what we can do is make sure that
it's not returned as -1, the
value's not returned as -1,
and then append it to the URI using a
contractBuildUriFromId method, if
that's the case. Now if the return value
is -1 then the insert failed,
and we saw that in the documentation for
the db insert method. In that case we'll
throw an exception, just briefly again, it
talks about here "or -1 if an
error occurred". So let's write the code
for that. So under the recordId we're
going to put if
recordId is not equal to -1, then
we're going, actually what we need to
do there also - and I'll talk about this
in a moment - we need to put the L
after the -1 to say that it's a
Long. So if it's not equal to
-1 you're going to put returnUri
equals TasksContract.buildUri
FromId recordId. Then what we're going
to do is have an else there, and the else
will be else throw, in this case we're
going to throw an SQL exception with the
message "Fail to insert, Uri was
Łuri". So that's essentially
the logic there, and I'm going to take a copy
of this code because it's very similar and I'll
paste that for the TIMINGS, the TIMINGS
code, and I'm going to change Tasks
Contract here. That's going to be equal
to TimingsContract. Now in relation to
this -1L, Kotlin doesn't allow a
comparison between long and int, which is
why we have to compare our recordId to
-1l, to specify the long value
-1, and you saw that there was an
error there if I left the L out. And as you
saw the code for the TIMINGS case is
almost identical. You just use the
constants from TimingsContract instead
of TasksContract. You can see that I've
made those two changes to cater for the
fact that that's different to the Tasks
Contract for the tasks component. And
just to finish off now we need to come
down here. We need to return something so
we're going to do some logging first. So
Log.d parentheses TAG comma, and we'll
type Exiting insert, returning $
returnUri, and we're just going to return
returnUri.
that's essentially our insert method. The
update method, which we'll start working
on in the next video, is a mix of query and
insert. It's code will be very similar to
query but we won't return a cursor. Now
when inserting, we're only inserting a
single record at a time.
When updating though, we may update the
columns of a single row, or in fact we
could update many rows at once. You might,
for example, want to update the Sort
Order column for all tasks with, say the same
description. So we'll start working on
writing that update function in the next
video.