Using an ArrayAdapter with LiveData. - android

I'm familiar with MVVM in WPF, but am in the process of porting an existing Android app to make use of a ViewModel class and LiveData for the first time.
The app "as/was" has a custom ArrayAdapter that is used to correctly display the items contained in a List in a gridview. The code that does that looks essentially like this ("essentially" because there are actually 4 such grids in the activity that all do it like this):
ArrayAdapter<String> playerAdapter = new MyAdapter<String>(this, R.layout.grid_item, playerList);
gridView.setAdapter(playerAdapter)
and when the playerList contents changed, there was an explicit call to
playerAdapter.notifyDataSetChanged()
Since my goal is to have the grid respond automatically when the playerList changes, and never have to notify anything, it seems now that the "playerList" needs to become a MutableLiveData<List<String>>.
I suspect I am going to have a problem though-
The MyAdapter class extends ArrayAdapter<T>
So in practice it's going to become an
ArrayAdapter<MutableLiveData<List<String>>>
But a MutableLiveData isn't going to work there, right?
I could unwrap it and make the call to create it look like this:
ArrayAdapter<String> playerAdapter =
new MyAdapter<String>(this, R.layout.grid_item, playerList.getValue());
Is this correct? It seems the way of madness.
Ultimately my goal is to just assign the gridView's android:adapter property to this thing in xml and never have to think about it anymore (beyond the required calls to setvalue() and postValue() when I modify the underlying list...which I'm not even sure how would work here.
I would hope there's already a MutableLiveData Collection that's ready to go somewhere and I'd use that.
What the standard way to do this?

I believe the way you are thinking is wrong. Don't worry about this:
ArrayAdapter<MutableLiveData<List<String>>>
You will get a list thats all. MutableLiveData is to set a livedata object like setValue() or postValue() manually. Just set your below code whenever liveData gets triggered and gives you the new playerList, something like this:
viewModel.getPlayListObservable().observe(this, new Observer<List<String>>() {
#Override
public void onChanged(List<String> playerList) {
ArrayAdapter<String> playerAdapter = new MyAdapter<String>(this, R.layout.grid_item, playerList);
gridView.setAdapter(playerAdapter);
playerAdapter.notifydataChanged();
}
});

Related

How does an Android adapter decide what to re-render?

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.

Live Update ListView

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!

declare Array adapter in the non-activity class to use in the fragment

So here is my problem. I'm using a dialog fragment to display some info. In that fragment, i have a spinner view that is filled up by the Array list. Normally I create that adapter in my fragment like this:
ArrayAdapter<String> teamsAdapter = new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_spinner_item, Variables.teamNames);
teamsAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Variables.spinner.setAdapter(teamsAdapter);
Variables is my user-defined class where I keep all variables.
Now here is my question. I have around 10 DialogFragments and almost all of them have spinner. I would like to define that Adapter in my Variables class and whenever i need it, i would just call it from my variables class. Unfortunately to do that, i need getActivity() in my adapter constructor. I cannot go around it.
BTW, is it a good idea or I should use interface?
It would be awesome if somebody could actually tell me why would I use java interface in android?
Your fragment would still need to call some Variables method to retrieve the spinner and to add it to your layout, right? So when you do that call, you can pass the Activity as Context, something like this:
ArrayAdapter<String> teamsAdapter = Variables.getAdapterForTeams(getActivity());
The question about the Interface isn't an Android but a Java question. If it makes sense to use an Interface in Java it will make sense to use it in Android. There's plenty of literature and online resources about Java that will explain the why and when Interfaces should be used. I'm sure if you search online you'll find plenty of resources about it.

Getting all of the items from an ArrayAdapter

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.

adapters notifyDataSetChanged does not work

EDIT 2:
I did solve my problem, but i don't know how:S I was moving my code snippets a bit around, a suddenly it worked. Must have done something in the wrong order, but its weird, checked it many times. Thanks for you help, and sorry I can't post an answer ;)
Hi.
I have a list view which I'm trying to refresh to update it self when i add an element to
the underlying array list.
Here is the code snippet:
private void addEvent() {
arrlEvents.add( event );
adptEvents.notifyDataSetChanged();
updateSaveFile();
filterList();
}
The arrlEvents is the underlying arraylist with the events, and im adding one event, trying to update the list view with notifyDataSetChanged(), but it doesnt work. Can anyone help?
Thanks for your time:)
EDIT:
Here is the source code for the adapter:
private ArrayAdapter<Event> adptEvents;
adptEvents = new ArrayAdapter<Event>( EventCalendar.this, R.layout.list_items, arrlEvents );
I have seen that sometimes it just randomly doesnt notify the adapter.
Try using adptEvents as protected or public on a global scope.
I have found that when that doesnt work. You can just re set the adapter again, just substitute the notifyDataSetChanged() for:
adptEvents = new ArrayAdapter<Event>( EventCalendar.this, R.layout.list_items, arrlEvents );
Edit:
Heres a code snipper from an App I wrote that works.
Class definition:
public class ClassName extends ListActivity implements AdapterView.OnItemSelectedListener {
Global Variable:
CustomAdapter adapter;
in OnCreate():
adapter = new CustomAdapter(this,R.layout.layout_name,dataSet);
setListAdapter(adapter);
Whenever I need to notify
adapter.notifyDataSetChanged();
There is no persistent link between arrlEvents and the adptEvents.... the latter simply initialises itself with the elements from the former. adptEvents has no way to know when arrlEvents changes.
To add new items you should call adptEvents.add(event) and not bother calling notifyDataSetChanged() explicitly, since ArrayAdapter.add() does that for you automatically.

Categories

Resources