I am using a list view to display user submitted comments. This is naturally backed by an ArrayAdapter for displaying. Now there can be many, many comments and I don't want that all the thousand comments are preserved in memory.
Do I really have to worry about this or does Android take care of this itself? If not, how can I best do this. The only strategy I came up with when a new comment is added:
check size of the list
remove one comment if it is > threshold
add new comment
clear adapter
reassign list to adapter
I disagree with the other answer. Sounds like you will be better off subclassing BaseAdapter to write your own custom adapter solution. It's a bit more work but would be the preferable solution. The moment you need to modify the behavior for one of the mutating methods of an ArrayAdapter is the moment you open yourself up to possible problems. It's also not recommended to track the list used to construct your ArrayAdapter backed solution. For one, there's no guarantee that the list reference remains the same...and in fact can change. So it makes keeping your list in sync with ArrayAdapter rather difficult.
Also, I may be wrong, but I believe re-assigning a sublist to original list will fail. Check out this post. You'd be better off just calling items.remove(#); to remove an item.
But otherwise, the general solution would be (within your custom adapter) is to always check the size of your list when adding. If over the threshold, remove an item.
On your constructor, slice the list if it exceeds the limit.
public MyAdapter(List<Item> items) {
this.items = items;
sliceItems();
}
void sliceItems() {
//sort items first
if (this.items.size() > limit) {
this.items = this.items.subList(0, limit);
}
}
public void addItem(Item item) {
this.items.add(item);
sliceItems();
}
Related
I'm using a RecycleView with a Gridlayoutmanager. My app loads a lot of items when the user scrolls down.
LinkedList has a good performance when adding new elements, while my ArrayList would need to get constantly resized.
But I'm not sure about what RecycleView does in the background which would work better with an ArrayList and/or a LinkedList.
My adapter would be:
public class PhotosAdapter extends RecyclerView.Adapter<PhotosAdapter.PhotosViewHolder> {
private Context context;
private List<Photo> items;
public PhotosAdapter(Context context, List<Photo> items) {
this.context = context;
this.items = items;
}
//other code here
public void addAll(List<Photo> newItems) {
int beforeSize = items.size()-1;
items.addAll(newItems);
notifyItemRangeInserted(beforeSize, newItems.size());
}
}
So when I create a new empty adapter I can either do this:
new PhotosAdapter(getContext(), new ArrayList<Photo>());
or this:
new PhotosAdapter(getContext(), new LinkedList<Photo>());
And when adding new elements simply:
adapter.addAll(myPhotos);
So would a LinkedList work better in this case? What about RecycleView's optimalized scrolling? Does that work better with an ArrayList or a LinkedList?
Now the first question should be are you optimizing prematurely? Is this a critical part of your app and are you having performance problems?
Anyway ArrayLists will give you better performance in most situations. I'd recommend using it as default and only using linked lists if you want to insert data into the middle of the list.
Yes, ArrayLists need to resize the array when they get too big, but in most cases this won't offset the advantages you get.
Remember that get(int index) is O(n) when using LinkedLists vs O(1) when using ArrayLists. If you're really concerned about adding lots of elements often, you can give the ArrayList a large initial capacity so it won't have to resize too often.
Check out this talk from Bjarne Stroustrup if you're interested. https://www.youtube.com/watch?v=YQs6IC-vgmo
It doesn't matter. Plus, you can always just try both of them and measure the performance.
More likely, the issue lies with something else:
Are you retrieving the data on a background thread?
Are new items being added in between existing items? Or only at the end? If it's only at the end you could try pre-fetching the items when the user is close to the bottom. (Again, by firing off a background thread and listening for a callback)
Are your child views complicated?
Since the code mentions using List, I'm guessing it's a recyclerView with images. The problem could be bitmaps being constantly allocated (and deallocated) memory.
In any case, I recommend profiling. A difference as minuscule as using ArrayList instead of LinkedList doesn't matter in all but the most extreme cases. See what your garbage collector is doing. Look at which method takes the longest to run, try to find what's slowing down your app the most.
I have dug deep down into SO but, although I have found other people asking similar questions to mine, I have not yet find a question that addresses the same issues I have. I have not found a satisfying answer either.
I have a ListView. When I call from the adapter, .notifyDataSetChanged, the ListView is updated, but I can see the update only once onResume() is called. In other words, I do not see it instantly, only after I leave the activity and comeback.
What can I do to see the update instantly? I have tried the .notifyDataSetChanged method, I have tried resetting the adapter... nothing worked.
According to your comment, you dont update the array IN the adapter, but an array held by the activity you passed to the adapter once. Thats why the adapter isnt updating properly. You are changing the array outside of your adapter-class, which might not be the same array-object your adapter is using. At onResume(), your adapter is recreated with the new array and showing the new content.
A solution would be using the following custom Adapter class:
class MyAdapter extends BaseAdapter {
private Array[] myArray;
public MyAdapter(Array[] myArray) {
this.myArray = myArray;
}
public updateContent(Array[] myNewArray) {
this.myArray = myNewArray;
this.notifyDataSetChanged();
}
// your getItem, getView, and so on methods
}
Then from your activity, simple call myArray.updateContent() with your new Array and it will update immediatly.
Its never good to hold and manipulate an object used from one class (the adapter) within another one (the activity). Try to move all code for manipulating the array into the adapter and use methods to add/remove items. This will make it a lot easier finding this kind of errors!
)
I found a tutorial about using the ListView in android. But I have a question about it. Here the tutorial:
http://www.vogella.com/articles/AndroidListView/article.html#listadvanced_interactive
You have to scroll to section 13.2.
The idea of the tutorial is on one hand you have a ListView (with checkboxes in each item), on the other hand you have an ArrayList (the items of the ArrayList are objects, which contain the information which are displayed in the items of the ListView, e.g. CheckBox checked or not, text etc.). The adapter schould keep both things equal. If you change the ArrayList, the ListView will be changed, too.
But now my question. If the user touch on one item of the List, the adapter will call the method "onCheckedChanged". But what happen there? An object will create there and get a tag from the the CheckBox. Ok. Now the method is done. The garbage collector will destroy the object or doesn't? What is when I need this infomation from there in my Activity. Imagine I have a button "Delete" under my list. So I have to transfer these information from the listener of the adapter to my Activity. How I can ensure that I use the same ArrayList with the right information in every class?
#Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
Model element = (Model) viewHolder.checkbox
.getTag();
element.setSelected(buttonView.isChecked());
}
I hope you know what I mean. Can you explain it to me please?
Sorry for the language, but english isn't my mother tongue.
Bye
If I understand your question, the response is in the Chapter 14.1 of the tutorial you gaved...
If you want to do some treatment when delete is checked you add a listener in the ListView.
These functions normally have the position clicked and you can do whatever you want knowing the position clicked. For exemple:
list.setOnItemLongClickListener(new OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(MyList.this,
"Item in position " + position + " clicked",
Toast.LENGTH_LONG).show();
// Return true to consume the click event. In this case the
// onListItemClick listener is not called anymore.
return true;
}
});
I'm not totally sure I understand you're question. There's a lot to unpack in there but I will give it a shot. To understand what's going on you need to understand the relationship between the ListView and the data that backs it which is contained in the ListAdapter.
The ListView displays the data contained in the ListAdapter. The ListAdapter gets its data from some other source, in this case its the ArrayList that you're referring to. Why use an ArrayList? In the tutorial its not entirely clear because the example uses a fixed set of data (e.g. a list of operating systems) that are written out as strings. In practice, however, often times you are using a dynamic list, like a list of users, places, whatever.
The ArrayList holds the objects that will eventually be displayed. This is the "models" class in the tutorial. The same applies for any other class of data, like a list of places. If you imagine a place as a class, that class would contain fields that describe the place (e.g. address, a description, some other unique features). As data is downloaded from some location, a new instance of place would be created for each location. Those objects would be collected in an ArrayList. When the downaloding process was finished, you would then begin the process of passing the contents of that ArrayList to the ListAdapter so that the ListView could eventually be updated.
How do you know you will always use the right ArrayList? Because your ArrayList is linked to you ListAdapter. The data the the ListAdapter processes, whether a list of models or users, is supplied by the ArrayList. You cannot use the wrong one. The same goes for the ListAdapter. Because you must set the adapter that the ListView is linked it it will always pull from the correct source. You cannot use the wrong data.
What's happening with the onCheckChanged listener, if you pay close attention, is that the user is really just updating a field contained in the model class. Each modle has an isChecked field. When the check box is clicked, that value for the object is updated. This is how you know what is checked and what is not.
Response to: "Nobody told the adapter "Hey it belongs to the list, which is your source"
Look at the documentation for ArrayAdapter
and for ListView
The constructors for the ArrayAdapter all take some list of object as an argument. This list would be the list of object that will be displayed within the ListView.
Additionally the ListView requires that you call setAdapter which takes some ListAdapter as its argument. This is what actually tells the ListView the source of the data it is going to display.
In that sense, the List, ListAdapter, and ListView are all linked together.
I'm very surprised there is such a small amount of info on drag and drop sorting with a cursor adapter and list adapter.
The closest post I have found on stackoverflow is this one:
https://stackoverflow.com/a/5047618/317889
But, it's not clear to me how to implement what CommonsWare suggests - clarification would be very helpful.
So far I am binding the cursor data to a list adapter and setting this as follows:
mMyCursorAdapter = new MyCursorAdapter(getActivity(), null);
setListAdapter(mMyCursorAdapter);
getLoaderManager().initLoader(0, null, this);
The list is generated but I now wish to add drag and drop functionality to the list items.
I would like to know the best way to go about this from an architectural point of view and any pointers as to how to go about the development of the core functionality would also be useful.
This blog post by Jason McReynolds (including a sample project) helped me a whole lot.
It explains how to use Carl A. Bauer's Library Drag-Sort-ListView with a CursorAdapter and SqLite. It shows how to save the the ListView's newly ordered state in the database as well.
This can definitely be achieved and lucky for you most of the work has already been taken care of, but you will need to modify a class slightly to meet your specifications.
The default Android music app has all of the classes you'll need.
First, you'll need to grab their custom ListView that allows for dragging and dropping.
That can be found here - TouchInterceptor.java.
You'll also need to grab their custom Cursor that's used to actually move the items in your ListView. It's an inner class called NowPlayingCursor.
That can be found here - TrackBrowserActivity.java
NowPlayingCursor extends AbstractCursor and its used to return the queue. The method makeNowPlayingCursor() is specifcally where you'll write most of your own code. Instead of returning the queue, you'll need to return the items you interested in moving, whatever they may be.
In order to use the TouchInterceptor, you'll need to implement TouchInterceptor.DropListener.
private TouchInterceptor.DropListener mDropListener =
new TouchInterceptor.DropListener() {
public void drop(int from, int to) {
final NowPlayingCursor mNowPlayingCursor = (NowPlayingCursor) YOUR_CURSOR;
mNowPlayingCursor.moveItem(from, to);
// Call `notifyDataSetChanged` here.
}
};
You should also look their moveQueueItem method used to move an item from one index to another. This method is used in the NowPlayingCursor when onMove and moveItem are called.
That can be found here - MediaPlaybackService.java
So, there's some work to be done on your part, but this definitely possible.
Here is a library that hopefully will solve your problem, it enables drag and drop reordering of list items.
Has an excellent demo that includes use cases for Fragments and Cursors
https://github.com/bauerca/drag-sort-listview
Key features:
Clean drag and drop (no visual glitches; I hope!).
Intuitive and smooth scrolling while dragging.
Support for heterogeneous item heights.
Public startDrag() and stopDrag() methods.
Public interface for customizing the floating View.
I have a ListFragment backed by an ArrayAdapter that gets populated by a Loader. When the user clicks on one of the items, I want to pass a reference to the selected item, as well as the rest of the list items to another fragment. My question is how should I get all of the items from the adapter? Here are the possibilities that I see:
1. Keep a reference to the backing List
Create the adapter like so:
List<DomainObject> items = new ArrayList<DomainObject>();
listAdapter = new ArrayAdapter<DomainObject>(getActivity(), R.layout.mine, items);
and then simply pass items or a copy of it to the next activity.
The downside I see of this is that I'm relying on the undocumented fact that the same list that I pass to the constructor contains the items later on.
2. Iterate through the adapter
When an item is clicked, iterate through the adapter and build up the list. This seems like an unnecessary amount of work. The items are contained in a List in the adapter and I'm manually copying each item to a new list.
3. Keep a separate list of items when adding to adapter
Before adding an item to the adapter, add it to a separate list that I maintain in the fragment. This is also wasteful as the list of items is copied in the ArrayAdapter and the fragment.
I'm a little late to the game, but I've run up against a similar issue.
One way to deal with #1 would be to maintain the reference to the list within a subclass of ArrayAdapter, so that your reuse is controlled by the adapter object.
Something like:
public class DomainAdapter extends ArrayAdapter<DomainObject> {
private final List<DomainObject> items;
public DomainAdapter(Context context, List<DomainObject> items) {
super(context, R.layout.mine, items);
this.items = items;
}
public List<DomainObject> getItems() {
return items;
}
}
The solution that I've gone with in the meantime is just to not use ArrayAdapter. In cases where you're fighting against this API, it seems like it's better just to use the less fully-featured (and complex) BaseAdapter. You can read more about the decision to go with BaseAdapter instead of ArrayAdapter in this article: Android Adapter Good Practices.
A quick test says that method 1 works. It seems the quickest and cleanest, but since it is undocumented you may want to test it across the intended platforms and whenever they update in case the underlying structure of ArrayAdapter changes.
I am using compile SDK version 22 and min SDK Version 10.
The best method is to "keep a reference to the List" BUT not passing "items" variable/parameter to the Constructor:
List<DomainObject> items = new ArrayList<DomainObject>();
listAdapter = new ArrayAdapter<DomainObject>(getActivity(), R.layout.mine);
In this way you only instantiate the ArrayList as an empty array and you will have to manage YOUR list by yourself.
I think first method is best way to do this.
I dont think, Data would be original for the Another Activity. because, You would pass items through bundle, so the object is written on bundle first and then in next Activity we read from bundle.
However, if you are using some other way to pass the list, use list.clone() to create new Object, instead of passing original one.