I am working on an Android app project in one of activities I have a spinner that I used
https://github.com/miteshpithadiya/SearchableSpinner
as I wanted my spinner can be filtered by item name and it works perfect.
But I want to load a very large amount of items in my spinner (about 70000 items). My spinner can load this amount but it takes too long till my UI respond.
I know there are practices that suggest to use paging and other possible resolutions but I can not use them at this moment and also I tried to use a AsyncTask to make this process a background process but it doesn't work either.
Here is my code to load items in spinner by the way (ArrayList goods could contain 70000 items) :
private void prepareProductsSpinner() {
productsSpinner = findViewById(R.id.spinnerProduct);
ArrayList<Good> goods = (ArrayList<Good>) Good.listAll(Good.class);
//fill data in spinner
ArrayAdapter<Good> adapter = new ArrayAdapter<>(AddEditAssetTakingItemActivity.this, android.R.layout.simple_spinner_dropdown_item, goods);
productsSpinner.setAdapter(adapter);
productsSpinner.setTitle("Choose an item");
productsSpinner.setPositiveButton("Select");
}
How can I solve this ?
You can display arraylist in spinner recycleview. The RecyclerView is much more powerful, flexible. it doesn't freezes the UI. It supports the use Viewholder pattern and can contains 100k+ rows it runs very smooth.
Spinner with recylerview.
This question is about Couchbase lite (no Sync Gateway).
I'm new to Couchbase, I managed to use the demo app, but I don't understand it completely.
It contains this code which (as far as I understand, since I'm not native English speaker) retrieve views to populate a listview with the indexes:
// This code can be found in ListsActivity.java
// in the setupViewAndQuery() method
com.couchbase.lite.View listsView = mDatabase.getView("list/listsByName");
if (listsView.getMap() == null) {
listsView.setMap(new Mapper() {
#Override
public void map(Map<String, Object> document, Emitter emitter) {
String type = (String) document.get("type");
if ("task-list".equals(type)) {
emitter.emit(document.get("name"), null);
}
}
}, "1.0");
}
listsLiveQuery = listsView.createQuery().toLiveQuery();
Could anyone give me a hand with what each part is doing?
In which step is the listview populated
Can I change "list/listsByName" in the code (line 3)? What would happen?
Can I emit more than one element?
The code is a little bit convoluted. Let's answer the easy parts first.
Can I change "list/listsByName" in the code (line 3)?
Yes. That's just the name of the Couchbase View. You choose the View name. Unfortunately the terms used in Couchbase and Android overlap some. A Couchbase View is a kind of static index of your database.
Can I emit more than one element?
Yes. You can emit most anything you want. Take a look at the documentation here
Now, tracing how the Android ListView gets updated:
In ListsActivity.java notice in the onCreate method a ListAdapter instance gets added to the ListView. This ListAdapter is a private inner class that extends LiveQueryAdapter.
LiveQueryAdapter is in the utils subpackage. If you look at its constructor, you'll see it adds a change listener to the query passed in. When triggered, this change listener sets an enumerator equal to the rows passed back by the live query, then calls notifyDataSetChanged to tell the list to refresh itself. That, in turn, causes getView in ListAdapter to get called. That's where the data is pulled from the database and used to populate a list entry.
This is a question about RecyclerView internal behavior for someone that knows its mechanics or is willing to dig into the source code. I’d like an answer backed up by references to the source.
Original question
(scroll down to ‘In other words’ for a more focused question)
I need to understand how notify* actions (for example, notifyItemInserted()) are enqueued. Imagine I have an adapter backed up by this list:
ArrayList<String> list = Arrays.asList("one", "three", "four");
I want to add the values zero and two, that are missing.
Example 1
list.add(1, "two");
// notify the view
adapter.notifyItemInserted(1);
// Seconds later, I go on with zero
list.add(0, "zero");
// notify the view
adapter.notifyItemInserted(0);
This is pretty straightforward and clear, nothing to tell.
Example 2
But what if the two actions are very close to each other, and there’s no layout pass in between?
list.add(1, "two");
list.add(0, "zero”);
What should I do now?
adapter.notifyItemInserted(1);
adapter.notifyItemInserted(0);
Or maybe
adapter.notifyItemInserted(2);
adapter.notifyItemInserted(0);
? From the adapter perspective, the list immediately switched from one, three, four to zero, one, two, three, four so the second option seems more reasonable.
Example 3
list.add(0, “zero”);
adapter.notifyItemInserted(0);
list.add(2, “two”);
adapter.notifyItemInserted(...)
What about it now? 1 or 2 ? The list was updated immediately after, but I am sure there was no layout pass in between.
Question
You got the main issue, and I want to know how should I behave in these situations. The real case is that I have multiple asynchronous tasks ending up in an insert() method. I can enqueue their operations, but:
I don’t want to do that if there’s already an internal queue, and there surely is
I don’t know what happens if two actions happen without a layout pass in between, see Example 3.
In other words
To update recycler, 4 actions must happen:
I actually alter the data model (e.g. insert something into the backing array)
I call adapter.notify*()
Recycler receives the call
Recycler performs the action (e.g. calls getItem*() and onBind() on the adapter) and lays out the change.
It’s easy to understand this when there’s no concurrency, and they happen in sequence:
1. => 2. => 3. => 4. => (new update) 1. => 2. => 3. => 4. ...
Let’s see what happens between steps.
Between 1. and 2.: I would say it is the developer responsibility to call notify() immediately after having altered the data. That’s OK.
Between 2. and 3.: This happens immediately, no issue here.
Between 3. and 4.: This does not happen immediately! AFAIK. So it perfectly possible that a new update (steps 1 and 2) comes between steps 3 and 4 of the previous update.
I want to understand what happens in this case.
How should we behave?
Should I ensure that step 4 of the previous update did took place before inserting new stuff? If so how?
I thought about similar questions before, and I decided:
If I want to insert more than 1 item directly to end of list and
want to get a animation for all, I should:
list.add("0");
list.add("1");
adapter.notifyItemRangeInserted(5, 2); // Suppose there were 5 items before so "0" has index of 5 and we want to insert 2 items.
If I want to insert more than 1 item directly to end of list, but
want to get separated animation for each inserted item, I should:
list.add("0");
list.add("1");
adapter.notifyItemInserted(0);
mRecyclerView.postDelayed(new Runnable() {
#Override
public void run() {
// before this happens, Be careful to call other notify* methods. Never call notifyDataSetChanged.
adapter.notifyItemInserted(1);
}
}, mRecyclerView.getItemAnimator().getAddDuration());
If I want to insert more than 1 item to different position of list,
similar as 2.
Hope this can help.
So lets start from little intro to RecyclerView works with notify items. And works pretty simple with other list of saved ViewGroup items (ListView for ex.)
RecyclerView has Queue of View Items which already drawn. And doesn't know about any your updates, without calling notify(...) methods. When you added new Items and notify RecyclerView, it starts cycle for checking all Views one by one.
RecyclerView contains and drawn next objects
View view-0 (position 0), view-1 (position 1), View-2 (position 2)
// Here is changes after updating
You added Item View view-new into (position 1) and Notify
RecyclerView starts loop to check changes
RecyclerView received unmodified view-0(position-0) and left them;
RecyclerView found new item view-new(position 1)
RecyclerView removing old item view-1(position 1)
RecyclerView drawing new item view-new(position 1)
// In RecyclerView queue in position-2 was item view-2,
// But now we replacing previous item to this position
RecyclerView found new item view-1 (new position-2)
RecyclerView removing old item view-2(position 2)
RecyclerView drawing new item view-1(position 2)
// And again same behavior
RecyclerView found new item view-3 (new position-3)
RecyclerView drawing new item view-1(position 2)
// And after all changes new RecyclerView would be
RecyclerView contains and drawn next objects
View view-0 (position 0), view-new (position 1) view-1 (position 2), View-2 (position 3)
It's just main flow of working notify functions, but what should know all this actions happens on UI Thread, Main Thread, even you can calling updating from Async Tasks. And answering you 2 Question - You can call Notify to the RecyclerView as much as you want, and make sure, you action would be on the correct Queue.
RecyclerView works correct in any usage, more complicated questions would be to your Adapter work. First of all, you need to synchronize you Adapter action, like adding removing items, and totally refuse of index usage. For example, it's would be better for your Example 3
Item firstItem = new Item(0, “zero”);
list.add(firstItem);
adapter.notifyItemInserted(list.indexOf(firstItem));
//Other action...
Item nextItem = new Item(2, “two”);
list.add(nextItem);
adapter.notifyItemInserted(list.indexOf(nextItem))
//Other actions
UPDATE |
Related to RecyclerView.Adapter Doc, where you can see functions same with notifyDataSetChanged(). And where this RecyclerView.Adapter invokes child items with android.database.Observable extensions, see more About Observable. Access to this Observable Holder is synchronized, until View Element in RecyclerView release usage.
See also RecyclerView from support library version 25.0 Lines 9934 - 9988;
It should not be a problem if you make multiple updates between layout passes. The RecyclerView is designed to handle (and optimize) this case :
RecyclerView introduces an additional level of abstraction between the
RecyclerView.Adapter and RecyclerView.LayoutManager to be able to
detect data set changes in batches during a layout calculation. [...]
There are two types of position related methods in RecyclerView:
layout position: Position of an item in the latest layout calculation. This is the position from the LayoutManager's
perspective.
adapter position: Position of an item in the adapter. This is the position from the Adapter's perspective.
These two positions are the same except the time between dispatching
adapter.notify* events and calculating the updated layout.
In your case the steps are :
You update the data layer
You call adapter.notify*()
The recyclerview record the change (in AdapterHelper.mPendingUpdates if I understand the code correctly). This change will be reflected in ViewHolder.getAdapterPosition(), but not yet in ViewHolder.getLayoutPosition().
At some point the recyclerView apply the recorded changes, basically it reconcile the layout's point of view with the adapter's point of view. It seems that this can happen before the layout pass.
The 1., 2., 3. sequence can happen any number of times as long as 2. immediately follows 1. (and both happen on the main thread).
(1. => 2. => 3.) ... (1. => 2. => 3.) ... 4.
Item firstItem = new Item(0, “zero”);
list.add(firstItem);
adapter.notifyItemInserted(list.indexOf(firstItem));
//Other action...
Item nextItem = new Item(2, “two”);
list.add(nextItem);
adapter.notifyItemInserted(list.indexOf(nextItem))
GreenDAO.
Which there is a simple way to display all the records from a Entity in the ListView, and is supported with the auto-update the list.
Perhaps Lazylist class? how to use it?
Have a look here. I'm using an adapter like this (including a ViewHolder-Pattern to reuse the Views inside the ListView) as well and it is fast even for a lot of records. But this won't be usable if you need auto-update-functionality.
Here are some information about LasyList to explain why:
Get LazyList using Query.listLazy():
This will not show new inserted records (or stop deleted records from displaying) automatically, since the records are cached in memory.
Thus updates won't be visible, because records are not queried twice.
Get LazyList using Query.listLazyUncached():
Updates of already existing records may be visible, but only if the records updated are currently not displayed.
Also you should be careful because I think inserting or deleting records may break this list.
To get inserts and deletes into the list you will have to refresh the underlying LazyList and call notifyDataSetChanged().
I'm using this in my Adapter:
public void setLazyList(LazyList<T> list) {
if (list != lazyList) {
lazyList.close();
lazyList = list;
this.dataValid = lazyList != null;
notifyDataSetChanged();
}
}
By the way: If you are using LazyList:
Don't forget to close LazyLists if you are not using them any more!
after some advice really. My app fills a list view on load using a mediastore cursor. This is pulling music linked to user defined folder, which in most cases will be all of their stored music. I have one beta tester that is using an Archos Tablet with approximately 10000 songs on it, running android 2.2. While performance for most users is pretty slick, I wanted to improve the experience for users such as this.
The current process:
User loads app.
App finds default folder
App populates list view with music within and below that folder
User moves to a folder further down the tree, list view is repopulated based on the selected folder
User moves again....list is repopulated based on the selected folder...
So what I'm wondering is this - is it faster/more efficient to use the following process:
User loads app
App finds default folder
app populates list view with music within and below that folder
user moves to a folder within the tree, THE LIST IS FILTERED TO THAT FOLDER
if the user moves higher up the tree than the default data (i.e. potential for new files), the list view is repopulated, but only in this circumstance.
So basically,my questions is "how does filtering compare to repopulation?"
A very good question. Let me try to answer this.
Filtering is actually repopulation the ListView, whereas you create/get a new collection and tell the Adapter it's content has changed by calling notifyDataSetChanged.
The 'heavy' work for a listView is that getView call in it's adapter. I've tested this myself, and if you inflate a new View every time getView is called, the performance drops. Heavenly.
The ListView's adapter is built so that already inflated views can be re-used, which tackles above named problem. Besides, only visible views are loaded, so it's not like the Adapter is going to create 10000 views if you tell it's collection is 10000 items big.
notifyDataSetChanged will tell the adapter to rebuild the listviews content, but it still contains previously inflated views. So here is a big performance win.
So my advice for you is, when you are using the same 'row layout' to just repopulate the ListView using notifyDataSetChanged. I've implemented this multiple times myself without noticing any UI performance issues. Just make sure to do the filtering of your collection an a background thread. (AsyncTask comes in handy here).
One last tip: Do you have any phone thats quite old? Or someone you know does? Find the slowest phone you can and test your application on it for performance. I have a HTC Legend myself, which is outdated and slow if f*ck, but perfect for performance testing. If it runs on my (old) phone, it runs on any phone.
Pseudo code sample if your applications flow:
public class FolderListActivity extends Activity implements OnItemSelected {
// NOTE: THIS IS PSEUDO CODE
private ListView listView
private Adapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstaceState);
// setContentView here
listView = (ListView)findViewById(R.id.your_listview_here);
listView.setOnItemSelectedListener(this);
}
public class AsyncLoadMusicLocationTask extends AsyncTask<Void, Void, List<String>> {
public List<String> doInBackground(Void... params) {
// Load the information here, this happens in the background
// using that cursor, i'm not sure what kind of things you are using
// So I assumed a List of Strings
}
#Override
public void onPostExecute(List<String> result) {
// Here we have our collection that was retrieved in a background thread
// This is on the UI thread
// Create the listviews adapter here
adapter = new Adapter(result, and other parameters);
listView.setAdapter(adapter);
}
}
#Override
public void onItemSelect(Some params, not sure which) {
// THIS SHOULD BE DONE ON THE BACKGROUND THE PREVENT UI PERFORMANCE ISSUES
List<String> collection = adapter.getObjects();
for (int i = 0; i < collection.size(); i++) {
// Filter here
}
// this method will most probably not exist, so you will need to implement your own Adapter class
adapter.setObjects(collections);
adapter.notifyDataSetChanged();
}
}