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.
Related
I am performing some reordering in a couple of array list, I have an adapter called
adapterMeasureEvi
which is set to a static ArrayList called measureEviArray from DataIpat class.
When debugging I can see that the static list is been assigned properly and it follows a notification to the adapter that the list has changed.
DataIpat.measureEviArray = (ArrayList<MeasureEvi>)measureEviArray.clone();
adapterMeasureEvi.notifyDataSetChanged();
Problem is, when getView() method gets called the first item it brings is from the old list, when I look up into the objects their indexes have changed that means I have updated the attributes but why is it still stuck on the old list?
/////EDIT////
I just noticed on the constructor of the adapter class that the list is definitely the old one.
public MeasureTableAdapter(Activity context, ArrayList<MeasureEvi> myMeasureEvi) {
super(context, R.layout.adapter_tablamedida_item, myMeasureEvi);
this.context = context;
this.myMeasureEvi = myMeasureEvi;
}
this constructor is called just once when the object is instantiated, so I suppose it means it will be stuck there, how can I update that list?
I think the problem is that when the data change, you recreate the DataIpat.measureEviArray instead of updating it. Hence your adapter will point to the old array, and the DataIpat.measureEviArray points to the newly upldated array. One way to fix your issue is instead of doing this (create a brand new array):
DataIpat.measureEviArray = (ArrayList<MeasureEvi>)measureEviArray.clone();
You should just update the DataIpad.measureEviArray array so that this array contains your new data (e.g. using clear and addAll to basically get the same effect as creating a new ArrayList).
Just stop cloning the list and work over the original worked, weird.
Deleted this,
DataIpat.measureEviArray = (ArrayList<MeasureEvi>)measureEviArray.clone();
not ideal but a workaround
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();
}
Say I have a List<User>. Now I can wrap this list in an ArrayAdapter.
List<User> users = Users.getAll();
ArrayAdapter<User> = new ArrayAdapter<User>(this, android.R.layout.simple_list_item_1, users);
I then bind the adapter to a listview to display the list of Users.
Users.getAll() uses Sugar ORM to query the database and return a list of users. Items can be added to the user list from the activity that displays the user list. I am wondering how do I keep the listview updated.
Option 1
One way is to manually update the users as a I add to the database and then call adapter.notifyDataSetChanged(). This works, but it doesn't feel right because I am maintaining a "fake" list that represents what is in the database.
Option 2
I am wondering how bad is it if I just clear the items in users, update it with the results of a new database query and then call adapter.notifyDataSetChanged()?
Will all the child views be thrown away and be re-rendered? Or does it call the equals() method to see if the models bound to each child is the same and then update only what is new?
Other Info
Since I am using SugarORM, I don't think I can get access to the Cursor to do something more efficient. However if there is a better way to keep the list synced with SugarORM, I am happy to hear that as well.
In answer to your option 2: No, it doesnt call equals, because the adapter works in conjunction with the widget to re-use the views, it doens't create a new view foreach item in the list, it create a view foreach visible item and as you scroll re-uses view that left the screen.
The best option here is to create your own adapter, creating a class extending BaseAdapter and creating your own logic inside it requerying the database and notifying the change to the listview (or gridview)..
On the other hand doing what you said here:
I am wondering how bad is it if I just clear the items in users, update it with the results of a new database query and then call adapter.notifyDataSetChanged()?
isn't bad either.
Create a DAO class that extends Observable, then have your Adapter implement Observer. Now every time you add or remove a SugarRecord, do through the DAO class and whoever is register as the Observer will get notified through the following method:
#Override
public void update(Observable observable, Object o)
You can more about Observable/Observer pattern here. This is just one of the many examples and tutorials out there.
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!
Consider the following code.
List<String> list = new ArrayList<String>();
ArrayAdapter<String> adapter = new ArrayAdapter<String>(context, resource, textViewResourceId, list);
// Method 1 : Add an item.
adapter.add("ITEM1");
// Method 2 : Add an item
list.add("ITEM2");
I was wondering, which is the correct way to add item into ArrayAdapter? As seems to me, both methods just work fine.
Method 1 updates the associated AdapterView, if you have already attached the ArrayAdapter to the AdapterView. Method 2 does not, requiring you to call notifyDataSetChanged() on the ArrayAdapter.
Typically, you populate the ArrayList before creating the ArrayAdapter, then use Method 1 to add new entries dynamically later on (e.g., based on user data entry).
This is what I do, especially for my Search Results page - where it grows as the user scroll down (list changes).
I'd keep a local ArrayList of Strings that is global to the class,
Initialize the adapter (also locally and globally),
Have a designated method alter the ArrayList of strings,
Then call the adapter.notifyDataSetChanged();
This will not only update your list and also update the adapter to work in sync.
Hope this helps,
best,
-serkan