Welcome back. Okay, we finished the last video by looking at the text that
we've hard-coded into the app. We should put the text into string resources, just
like we do for layouts. I'll start with the name TextView that displays the
heading. Click on the word "instructions" and expand the drop-down that appears, on
the left hand side. We want Extract String Resource from the menu, and we'll
call this instructions_heading. Click OK and Android studio
replaces the text with a string resource. Unfortunately it doesn't do a perfect
job, and we've got an error. This is because we don't have a context readily
available, to call the getstring function on. As it happens, we don't need one. The
setText function will accept a string resource ID instead. We can delete the
call to context.getString, and the extra parentheses, to get rid of the
error. Do the same thing in description. This time call the resource instructions.
And that didn't go well. You'll often find that long strings in code are split
up, like we've done here. But if you're in the habit of doing that, then it's a good
habit to break, when writing Android apps. You'll almost always want to extract the
string into a string resource, and that's much easier if you haven't split the
text up like this. Ok, I'll undo that change.
Now I need to put all the text back into a single line, then extract the resource
again. It's a bit fiddly, which is why I suggest you don't split the text up in
the first place. The light bulb can help here again. It has an option Convert
Concatenation to Template. That removes the concatenation, and we should now be
able to use a light bulb again to create the string resource. I say "should" because
I've been unable to get that option to appear. It's not because we used the
Convert to Template option either. At the moment, Android Studio won't provide the
string resource option for this text. Whether that's because it's too long, or
because it contains line break characters, I don't know. That might
change in a later version, but for the moment it doesn't work. As we've said
before, "life's too short to fight the tools". We know what the option should do
so let's just do it. I'll cut all the text for the description, without the
quote marks,
then open up the strings.xml file from resources values. I'll add the new
resource after instructions heading calling it instructions, and paste the
text. The right-click context menu has a paste simple option, which will paste the
text in, keeping the \ns instead of replacing them with line
breaks. While we're here, there's a blank fragment resource that was created by the
wizard, when we created the add/edit fragment. We don't need that so it can be
deleted, along with the TODO comment.
Back in the adapter, we use the same name, instructions, as a resource ID. Okay the
next function we have to implement is getItemCount. The RecyclerView uses
this so it knows how many items there are to display. If there are no items,
we'll be sending back a view that displays the instructions. So we don't
want to tell the RecyclerView that there are no records, we'll always return at
least one.
we'll log the start
We'll create a variable called cursor of type Cursor to hold the cursor.
We'll make a count variable which is of type integer, and if it is either null or
it doesn't have any items in it, in other words the count is zero, we're going to
return a value of one. This is a bit of a fib
because actually we'll return a single view holder that's containing the
instructions that we want to show.
Otherwise let's return the count, then let's log the fact that we've finished,
and actually return the value. That's all the overridden functions done, but
there's one more function we still need to add. Google haven't created a basic
CursorRecyclerViewAdapter class, so we're modifying RecyclerView's Adapter
class. That's what we're extending, to produce this class. But the adapter
doesn't know about cursors, so it doesn't have functions to deal with them. The
CursorAdapter class has one additional function, and that's swapCursor. We're
implementing the functionality of a CursorAdapter that's tailored for a
RecyclerView, so it makes sense to use the same name for our function. We'll add
it after the getItemCount function, and then discuss what it's doing. We'll
comment our function, to help others that look at our code, and to help our future
selves.
We'll define our function to take a new cursor and return a cursor. If the old
cursor and the new cursor are the same, then we're not really doing a swap and so
we'll return null. We'll save the current itemCount, and we'll save our old cursor.
We'll create our new cursor, and if that new cursor isn't null then we'll notify
the observers about the new cursor.
Otherwise we'll remove all the items, and notify the observers that that has
happened. Finally, we'll return the cursor as promised. This basically allows the
adaptors cursor to be swapped, when the underlying data changes and has to be
requeried. This is boilerplate code. You'll find examples like this a lot online. It
should be called whenever the cursor that the adapters using is changed.
We'll see it being used in MainActivityFragment, when we provide a valid cursor
for the first time, and then whenever the data changes. This function returns the
previous cursor, which can be useful if the owner of the cursor needs to close
it. In code that uses a CursorLoader, we didn't have to worry about this because
the CursorLoader class takes care of it. But that's how the CursorAdapter does
things, and I've stuck with the standard code so that it can be used with a
custom Loader if needed. As it turns out, we do need to close the old cursor; and
now that the Loader classes have been deprecated, we lose the functionality
that they provide. The Loader took care of things like closing old cursors, and
also made sure that database queries were executed on a background thread.
Don't feel too bad that you can't use them though, they were a bit cumbersome
to set up, and things aren't any harder now that we can't use them.
in fact I think things are a bit easier now, as you'll see when we come to create
our ViewModel. But I'm getting ahead of myself, back to our code. I'll be
discussing these 2 notify functions later. Remember we still have to make a
change to our ContentProvider, so the RecyclerView shows updates to the
database. Basically, what these calls do is notify any observer - such as the
RecyclerView - that the data from this adapter has changed. If the new cursor
isn't null, then we use notifyDataSetChanged but if it is null then we pass the entire
range of records - that's 0 up to the value of getItemCount - when we call
notifyItemRangeRemoved. This signals that the entire range of records has
gone. And that's our adapter finished, for now anyway. We've still got to deal with
the buttons being tapped, but we'll come back to that once we've got the RecyclerView
working. To get it working, we just have to link it up to this adapter, in
MainActivityFragment.
We'll have to refer to the adapter in several
places in code, so I'll start by declaring a field to store it.
We passed null as the cursor, because we don't have one that's available yet.
Passing null will cause the adapter to return a view containing our instructions,
and that's exactly what we want to happen, when the app starts with
no task records. I'll add the code to attach the adapter to the RecyclerView
in onViewCreated.
Let's set our LayoutManager to a LinearLayoutManager and
attach the adapter. We gave our RecyclerView the ID task_list and
we're referring to it using the synthetic import. You won't see any data
yet, but we can run the app and make sure we haven't broken anything. If all goes
well, we should see the instructions displayed.
okay I'll stop this video here. In the next video we'll write the ViewModel
class that will populate our adapter's cursor. See you in the next video.