Xamarin Android - Build a Gridview With Infinite Scrolling
I needed to implement a GridView display, which had two columns of items. Each item consisted of ImageView
for a small thumbnail image (160x160), above a small TextView
with a basic description. Easy enough, and there’s a ton of examples out there on how to do it.
However, my list of items is extremely large (it runs into the thousands). There’s way too many items to load in one go, and even if I could, the data set is dynamic, and there’s more of them being added all the time.
This is a problem that’s already been solved by ‘infinite scrolling’ or ‘endless scrolling’. If you’ve ever used any form of mobile app or website which has a large number of items in a list, you’ll already be familiar with the concept: Basically you’re shown an initial list of items, but as you scroll down, more items are loaded automatically, and displayed. All you have to do is just keep on scrolling…
How to get this working in a GridView though?
It was surprisingly difficult to find information on this. There were tons of examples showing how to do it in a ListView with ListAdapters and ArrayAdapters, but all the ones I was looking at had a static list of items. They’d start out with a list of items, and not add any more to it. Great to demonstrate how a GridView works, but infinite scrolling… not so much.
The basic principles:
- Load the first set of items
- Capture something from the scrolling event which said “we’re getting close to the end, go and get more items!”
- Load the items, hopefully quickly enough so the user doesn’t bump up against the bottom of the current list.
- Tell the DataAdapter to notify everything attached to it that its data set has changed (coz it’s got new items in it).
- Tell the GridView to invalidate its views (i.e. cells) which causes it to repaint everything (and update the scrollbar proportions, list count indicator, etc.)
Getting Started
To get started in this demo, we first need a backing data store which has the ability to load more items on demand. This could be a web service, an API call, a database query, an Observable collection of some sort, pretty much anything.
For the purposes of this example, I’m just going to use a generic list of MySimpleItem
(as you may be able to tell from the name, it’s just a toy object).
I’m going to keep a list of these in an object which I’ve created called MySimpleItemLoader
, another toy object which has a method I can call whenever I like, to get more more MySimpleItems
. Easy:
Let’s just unpack this quickly:
- The
List<MySimpleItem> MySimpleItems
is obviously just my data store. - The
CurrentPageValue
is just a grouping counter we can use to determine how far into the dataset we are, to get the next set (‘page’) of items. - The
CanLoadMoreItems
property is a flag to indicate whether there are more items than the ones that were just loaded.
The CanLoadMoreItems
variable is pretty dimwitted. For the purposes of this demo it’s always going to be true. But normally you could do a quick check to see if the last number of items returned was less than the number you asked for; if it was, then there’s obviously no more items.
- As for the
LoadMoreItems
method… I’m sure you’ll figure it out :)
So far so good.
Now we need something to act as a middle-man between the GridView and the list of MySimpleItems. Enter the DataAdapter class of object. This acts as a kind of bridge between the list of data on the one side, and the display of it, on the other. It also handles things like recycling of views to save and reuse system resources, a topic I’ll go into in a future post where I’ll show how to display more than one kind of item in the grid, speed up the display, and so on.
Setting Up The Data Adapter
There are many different kinds of DataAdapters, but we’re going to use a derivative of the standard BaseAdapter
, which I called (imaginatively) MyGridViewAdapter
. And here it is:
First I inherit from BaseAdapter<MySimpleItem>
. This just makes it a little bit easier for the data adapter, means less object casting for us, and lets the parent of the BaseAdapter
know what kind of objects it’s going to have to juggle internally so it can do all the things we shouldn’t have to care about.
In the constructor we pass in an instance of the MySimpleItemLoader
(which will have the first set of items already in it).
We also pass in the the Android application Context
. This is only so we can use it in overridden GetView
method to help us inflate the correct type of grid view item display resource for each item in the grid, as it scrolls into view. More on this in a bit.
And then we just have to override a couple of abstract methods (which the BaseAdapter
will force us to do), because it’s stuff that it needs us to implement.
Pretty standard. These methods just provide you with places you can do additional work, if you need to.
In our case the only ones we’re really interested in are the Count
property, which tells the adapter how many items there are currently in the list. I guess it helps the adapter allocate memory, set the correct proportions on the scroll bars, stuff like that. This guy gets invoked every time we fire a NotifyDataSetChanged
at the data adapter, to say ‘hey, you’ve got more items in your data thingy!’.
The MySimpleItem this
is just a self-referencing property which returns the MySimpleItem
at position
in the list.
Now, coming back to the GetView
method we’ve overridden. This is the core of the adapter and handles the linking of items in the MySimpleItemLoader.MySimpleItems
with the actual grid view display items. For the sake of brevity (and clarity), I’ve left out some of the performance-enhancing bits. I’ll cover those in another post. But for what we need, this will suffice:
It’s pretty straightforward:
- It gets the current
MySimpleItem
atposition
. - It checks to see if the
currentView
is null.currentView
is an instance of a grid view item which may have been used before, and been recycled. If it hasn’t been recycled, it’ll be null, and we need to make a new one. - If we need to make a new one, then here’s where we use our application
Context
to inflate the simple xml which describes our individual grid view item ‘cell’. In our case, it’s just anImageView
for a thumbnail, and aTextView
for theMySimpleItem.DisplayName
text. - If the
currentView
is not null, it means it’s come out of the recycling bucket, and we can just reuse it.
For more on the ‘Recycling’, check out the References section at the end of this post.
Either way, we now have a grid view item. The next step is just to locate the image and text view controls on it (using FindViewById
), and set the relevant property on each to the value that’s come from the item
variable.
As I mentioned before, there’s ways to speed this operation up. I’ve omitted them for now.
At the time of writing I didn’t have a list of images, or image urls, so I’m simply setting the ImageView
to the application’s icon.
With some of the attributes removed for brevity, the MyGridViewCell.xml
file looks something like:
(the full source is available in this GitHub repo)
And we’re done with MyGridViewAdapter
.
Displaying the Grid Items
Finally we need to make a GridView
, hook it up to the MyGridViewAdapter
, and monitor the GridView
scrolling events to tell our MySimpleDataLoader
to load more stuff.
We can do all of this in the standard MainActivity with just a few lines of code:
Nothing spectacular. It simply instantiates a new MySimpleItemLoader
and tells it to load the first set of items, ready for immediate display. Then it hooks up a new instance of the MyGridViewAdapter
with the application Context
and our loader instance to the GridView
.
And if you load it up at this point, you’ll get a grid view with 24 items in it.
It’ll scroll, but when you get to the bottom, it’ll just stop. That’s fine, if you only have 24 items. But we have an infinite list of items to display!
Adding The Infinite Scroll
We need to add one more method to this activity to get it to do this, and attach it to the GridView.Scroll
event. Easily done.
Add an event handler somewhere after you’ve instantiated the GridView
:
What does this do?
- The scroll event fires MANY times per second, so we need a way to ensure that the first event-on-a-thread that comes along has exclusive access to this method. Otherwise we’ll have a massive re-entrancy problem and maybe even a stack overflow (when was the last time you saw one of those?!). So we lock it across all threads for the duration of that method.
- There’s a quick check to see if we’ve reached the point at which we need to load more stuff. I’ve set an arbitrary threshold of 6 items from the end. We use a combination of
args.FirstVisibleItem
,args.VisibleItemCount
,args.TotalItemCount
andLoadNextItemsThreshold
to figure out where we are. - If we have reached the point at which we do need to load more items, we go and get them.
- Then we tell the
MyGridViewAdapter
that its backing data set has changed, so it can do any background maintenance work it needs to do, - Finally we tell the
GridView
to update whatever it’s currently displaying (which will also make sure the scrollbar is set to the correct proportions and position).
And if you load your items fast enough, the user won’t even notice. Obviously if you’re on a slow data connection or something else is bottlenecking your retrieval, you may need to set a higher reload threshold, so it goes earlier. Or temporarily display some sort of loading indicator so that your users don’t think they’ve reached the end of the list, or that your app has locked up.
And get your next set of items asynchronously!
That’s it. We’re done.
The source code for this example is sitting over in this GitHub repo
I initially started out with using an adapter based on the ArrayAdapter
. I’m dealing with a list of items, and an array is pretty close to a list, right? Man, that was a poor choice. Nothing I did could get the ArrayAdapter’s NotifyDataSetChanged method call to actually do anything. And without that, the GridView’s call to invalidate its views did nothing either. More items would load, but none would display. I found a workaround, where I’d recreate a brand new ArrayAdapter with all the new items in it, every time, and reattach it to the GridView. That works too, but it’s really clunky, and I felt dirty doing it.
References
Some references you may find useful:
- Romain Guy’s talk on making your Android App UI fast at Google I/O
- Xamarin Grid View sample
- James Montemagno Loading More Items
Everything in here is released as OSS under the MIT license, so feel free to use it any way you like.