Issue
I need to create an application that uses the recyclerView to show all the photos in the gallery.
An immediate solution could be to store the URI of all the photos in the gallery in a list and to pass the latter to the recyclerView (to retrieve the URIs I use a content provider). However, I was wondering if there was another more efficient method than allocating a few thousand objects when, in the end, the user will likely only see the first few photos.
Above I have presented only one example, but I would like to know if, in general, this is a problem and what solutions there would be.
Solution
Background
Well, there are two things:
RecyclerView
: This is a general idea that only loads views for as many viewable on the screen, plus a bit more to buffer ahead of time just in case the user scrolls there.Think of it like how video players buffer a minute worth of video ahead of time, but never loads the entire movie in the browser. It is the same fundamental idea.
Likely, a
RecyclerView
never loads the entire thing. Even when you scroll over the entire thing, it unloads the portion that is too far away, and only keeps visible items, and items near to the visible ones (as a king of buffer in case you scroll to them any time soon).RecyclerView.Adapter
: This adapter is inside of this bigger concept, and you have to implement it to interface with your database. You have to design it intelligently to avoid loading the entire database in memory.If you poorly implement this adapter, such as a lazy implementation that stupidly loads the whole database into memory, then you are mostly defeating the point. You will still benefit from the fact that graphically only a handful of items/cards are loaded in memory, but this saving is then lost when a poor adapter implementation wastes that saved memory to load tons of unneeded data.
So, you have to think of a design to only load portions of data as requested by the RecyclerView
. Why would you load thousands of URLs into the application? Makes no sense.
Just wait until RecyclerView
asks for them, then load them. RecyclerView
will, by itself, query a bit of extra surrounding data (photos in your case) to buffer them ahead of user's scrolling to them.
So you must not hand the full list of URLs either. You only hand URLs, o process them, when requested.
RecyclerView.Adapter
's queries
In Background, we said that RecyclerView
is efficient, and that you must not load the entire data to it, but rather wait until it asks you for it. However, a question is: how to implement a backend that efficiently pulls requested data? To answer this, 1st we have to look at the kind of queries that a RecyclerView
asks your backend.
Basically, RecyclerView
thinks of all elements in your database as a sorted list. So, when it wants to obtain the, say, 5th item in that sorted list that it imagines, it asks "hey, give me item number 4!" (it asks for index 4 because it counts from 0; this way item 4 is indeed the 5th in this imaginary ordered list).
Note that this ordered list is imaginary in RecyclerView
's imagination. Your data does not have to be this way, and may not be. But it is important to understand how RecyclerView
imagines the data is in order to implement a RecyclerView.Adapter
that connects RecyclerView
's imagination to your backend database.
Ideal database backend
The ideal case is if your database is structured as per RecyclerView
's imagination: A sorted list.
For example, imagine your URLs are stored in file names, each named like 100.json
, 200.json
, ..., n.json
. This way if RecyclerView
asks you "Hey, give me the image number 2,324!", your backend will simply read the file 2300.json
, which contains only 100 URLs, and from them picks the entry number 24, fetches the image that it points to, and then hands it to RecyclerView
to display it for the user.
With this structure, you are free to expand your database by appending your new URLs to the end of the database. So, if the last entry is image number 1,000,123, then you add the new URL as the 1,000,124. This is called push.
Likewise, deletions from the end is cheap. This is called popping. You push and pop basically.
However, this list has a problem: what if you'd like to insert somewhere in the middle? Or delete somewhere in the middle? Then it can be a disaster: if you have 1,000,124 entries, and want to delete entry 500, then you will have to decrement the identifier for all other images by 1. So, 1,000,124 will become 1,000,123, 1,000,123 will become 1,000,122, ..., 501 will become 500.
As you see, this ideal database for RecyclerView
is ideal for pushing and popping, but not ideal for in-middle insertions and deletions.
What should you do?
If pushing (aka appeneding-to-end) and popping (aka deleting-from-end) are all what you require, then the ideal database above is a perfect backend for your
RecyclerView.Adapter
implementation.If pushing and popping are not enough, so that you may require addition and deletion from, possibly, the middle, then you will have to devise an alternative plan.
One alternative plan is to have 2 separate components:
Save your data in a list like the above example. The list is initially sorted.
As you insert or delete from the middle, you create a separate tree structure to translate old index numbers to new ones.
For example, if you got 1,000,124+1 items, and you deleted item number 500, then, instead of decrementing the index number of all subsequent items 501, 502, ..., 1,000,124, you instead create a single rule that says _"if you get a query for any item
x >= 500
, you retrievex+1
.Why
+1
? Because we deleted once. If we delete twice in the same position that 500 used to live in, then we will increment the rule to bex+2
.But what if we delete/insert in other positions than 500, such as, say, 900? Then we will have for entries after 900, then we will have two index translation equations: one for those from 500 until 899, and another for 900 until the end.
As you see, this approach is initially OK, but in the long run it will fragment, and you may get a crazy large number of equations.
But, the good news is that
RecyclerView
itself is sequential. So your backend does not have to load all the equations separately. Instead, it will aggregate equations as it hits them. The total space in memory will be fixed (i.e. O(1) worst space time complexity).Why? Because
RecyclerView
's scrolling itself is limited to be O(n) in time by the very definition of "scrolling". So, when the user is scrolling, you don't worry about it.A cool thing is that, as the user is scrolling around, the backend can transparently summarise those translation equations into a decision tree. Then, in the future, if the user chooses to use a fancy API that allow him to jump around (instead of scrolling, by picking a calender date for example), the backend can use that decision tree to speed up its binary search operation.
Other alternative plans?
Yes. It all depends on the kind of operations that you perform on the backend database. I mentioned some thoughts to show you the kind of thoughts we should have in our minds in order to research the best solution for the specific requirements of the application.
In summary, it all depends on the distribution of read, insert, delete and update.
Any more detailed answer will require you to reveal information about the distribution of read, insert, delete and update. Without knowledge of such distribution, no one can tell you the best solution.
Answered By - caveman
Answer Checked By - Timothy Miller (JavaFixing Admin)