Overview:
I have an Expandable List View which displays items of the type ToDoElement.
Each ToDoElement item has one String for the Group-Element and 1-3 Strings for the Child-Elements
My custom List Adapter expListAdapter gets the informations about the group- and child-items with the methods createGroupList() and createChildList().
In the Method createChildList() i check how many Strings for the child-items are in the ToDoElement and create the child-items for the Adapter.
At the end I can sort my list by comparing the Strings for the group-items.
All of this works fine, but here is my Problem:
After the items in the List View are sorted correctly, the number of child-items is wrong.
That's because my List Adapter doesn't know that the number of child-items has changed. So some child-items aren't displayed and other child-items are just empty, because there is no data for them.
My Suggestion:
I know that somehow the methods createGroupList() and createChildList() have to be called again from my Adapter, but i don't know how to do it. I've tried expListAdapter.notifyDataSetInvalidated(), because somebody told me that this method would call createGroupList() and createChildList() again, but is doesn't work.
Code Fragments:
Definition of my List Adapter:
expListAdapter = new MyListAdapter(this, createGroupList(), createGroupList());
setListAdapter( expListAdapter );
createGroupList() and createChildList():
private List createGroupList() {
ArrayList result = new ArrayList();
for (int i=0; i < Liste.AnzahlElemente(); i++) { //
result.add(Liste.getSize(i).getGroupString());
}
return result;
}
private List createChildList() {
ArrayList result = new ArrayList();
for(int i=0; i < Liste.getSize(); i++) {
ArrayList secList = new ArrayList();
for( int n = 1 ; n <= 3 ; n++ ) {
if (Liste.getElement(i).getChildString(n).length() != 0){
secList.add( "- " + Liste.getElement(i).getChildString(n));
}
}
result.add( secList );
}
return result;
}
Sorting the List:
(Liste is an ToDoListe object, which manages the ToDoElements in an Array List)
Collections.sort(Liste.getListe(), new NameComparator());
expListAdapter.notifyDataSetInvalidated();
//expListAdapter.notifyDataSetChanged(); //same effect as notifyDataSetInvalided
my NameComparator:
public class NameComparator implements Comparator<ToDoElement>{
public int compare(ToDoElement item1, ToDoElement item2) {
return item1.getGroupString().compareTo(item2.getGroupString());
}
}
Thanks a lot for your help!!
I solved my own problem!
I wrote an Update method for the my custom List Adapter.
It looks like this:
public class MyListAdapter extends BaseExpandableListAdapter{
private ArrayList<String> ueberschriften;
private ArrayList<ArrayList<String>> stichpunkte;
public MyListAdapter(Context context, List _ueberschriften, List _stichpunkte) {
this.ueberschriften = (ArrayList)_ueberschriften;
this.stichpunkte = (ArrayList)_stichpunkte;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
// [...] some other methods
public void update(List _GroupStrings, List _ChildStrings) {
GroupStrings = (ArrayList<String>) _GroupStrings;
ChildStrings = (ArrayList<ArrayList<String>>) _ChildStrings;
}
}
Sorting the list in my activity looks like this:
Collections.sort(Liste.getListe(), new NameComparator());
expListAdapter.update(createGroupList(), createChildList());
expListAdapter.notifyDataSetChanged();
now the number of child-items also updates correctly!
cheers!
The list in your Adapter and the list displayed are separate lists.
In case the adapter is filled with new data or data is rearanged, you need to inform the view with notifyDataSetChanged() - this will trigger the redrawing of the view.
notifyDataSetInvalidate() should only be called in case your data is not available any more.
As described here:
Android ListView Adapter notifyDataSetInvalidated() vs notifyDataSetChanged()
Related
Hi guys is it possible that everytime I add an item in the database it will notify and update the listview? For example I have two fragments in one activity then When I send a data in the database from the Fragment A the Listview in Fragment B will update also without using interface.
There is a function of ArrayAdapter called - notifyDataSetChanged()
//getting the list view
ListView userListView = (ListView) findViewById(R.id.users_ListView);
//creating an array to represnt what ever you want
ArrayList<String> users = new ArrayList<>();
for (int i = 0; i < 10 ; i++) {
//populate the array
String s = "user " + i;
users.add(s);
}
//setting up adapter to the array (this is 1 row with 3 arguments)
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getApplicationContext(),
android.R.layout.simple_list_item_1,
users);
//connecting the list view to get the data to show from the adapter
userListView.setAdapter(adapter);
//changing the arrayList by adding or subtracting
String s = "another user";
users.add(s);
//update the adapter and list view with this command
adapter.notifyDataSetChanged();
Share your code if you are having any trouble.
good luck =)
I'm attempting to get method to handle updates to the ListView when SetWeatherData is called. Nothing ever shows up in my listview below. Any ideas? _rootView points to the right root and ListView comes back not null. m_weatherdata has a couple string elements in it.
Note the initial set of data does not show up either. Just blank.
I'm thinking it should be easier to setup a generic method to update a ListView when the data changes using straight up code.
private ArrayList<String> m_weatherdata;
private void SetWeatherData ( ArrayList<String> _weather)
{
m_weatherdata = _weather;
UpdateWeatherUI();
return;
}
ArrayAdapter<String> m_adapter = null;
private void UpdateWeatherUI()
{
if ( m_adapter == null ) {
m_adapter = new ArrayAdapter<String>(
this.getContext(),
R.layout.list_item_forecast,
R.id.list_item_forecast_textview,
m_weatherdata);
View _rootview = this.getLayoutInflater(null).inflate(R.layout.fragment_main, null, false);
ListView _listview = (ListView) _rootview.findViewById(R.id.listview_forecast);
_listview.setAdapter(m_adapter);
}
else
{
m_adapter.notifyDataSetChanged();
}
}
You are assigning a new ArrayList to your dataset.
m_weatherdata = _weather;
Instead add items to the dataset. Like this
m_weatherdata.addAll(_weather);
private void SetWeatherData ( ArrayList<String> _weather)
{
m_weatherdata.addAll(_weather);//change here
UpdateWeatherUI();
return;
}
When you set an adapter there is an observer attached to the
underlying data. So notifyDatasetChanged() only works if you only
modify the data in it.
If you want to clear all data from your dataset before adding new items to it, use the clear() method of ArrayList
private void SetWeatherData ( ArrayList<String> _weather)
{
m_weatherdata.clear();//change here
m_weatherdata.addAll(_weather);//change here
UpdateWeatherUI();
return;
}
m_weatherdata = _weather; // updates the local variable with new set of data. but adapter doesn't know about the changes made as you have created the instance of the adapter with array list by the following line of code.
m_adapter = new ArrayAdapter<String>(this.getContext(), R.layout.list_item_forecast,R.id.list_item_forecast_textview,m_weatherdata);
In you want to updated the data either call
m_adapter.addAll(newsetofstringtobeadded);
or
Create new adapter
This will update your list.
I am working on a XMPP based chat in android.. and I am struck at a point where I need to update the position of an item in the listview to the top in case a new.message arrives.
The use case is.. I am on Contacts screen of the app and a new message comes.. so this contact should move to top of the list and get bold. This is what is similar to whatsapp as well
How can this be done. My class imolemebts activity and i have implemented custom list adapter.
So howcan I find if an item exists in the listview and secondly how to dynamically change position
First, keep in mind that a ListView is just a representation of a list of Objects. So if you want to know if an item is in the ListView, you just have to check if the corresponding Object is in your list of Objects.
Is the same when you want to change the position of one item, you have to change the position of the Object in the list.
Start by defining these objects:
private ArrayList<MyObject> lists = new ArrayList<MyObject>();
private MyCustomAdapter myAdapter;
The first time you create your ListView, just do as usually:
//fill your list with your objects
lists.add(myObject1);
lists.add(myObject2);
lists.add(myObject3);
//create and set the adapter
myAdapter = new MyCustomAdapter(..., ..., lists);
myListView.setAdapter(myAdapter);
Now you can know if your lists contains a specific object (which is the same that checking if an item is in your ListView) by simply testing that:
lists.contains(anObject);
Then, if you want to change the position of a specific item in the ListView, you have to create a new list and put the elements in the correct order. You can use something like that (not tested but it should work):
private ArrayList<MyObject> moveItemToTop(ArrayList<MyObject> lists, int positionOfItem) {
if (lists == null || positionOfItem < 0 || positionOfItem >= lists.size()) {
return lists;
}
ArrayList<MyObject> sortedList = new ArrayList<MyObject>();
//add the item to the top
sortedList.add(lists.get(positionOfItem));
for (int i=0; i<lists.size(); i++) {
if (i != positionOfItem) {
sortedList.add(lists.get(i));
}
}
return sortedList;
}
Or even this (which is way easier...).
Finally, call these two methods to update your ListView:
myAdapter = new MyCustomAdapter(..., ..., moveItemToTop(lists, itemPosition));
myListView.setAdapter(myAdapter);
This is how I resolved it
private void moveMessageToTop(MessageObject message) {
int index = 0;
for (Friends friend : mFriends) {
if (friend.getName().equalsIgnoreCase(message.getFrom().split("#")[0])) {
index = mFriends.indexOf(friend);
break;
}
}
if (index != 0) {
mFriends.add(0,new Friends(message.getFrom().split("#")[0], message
.getMessage()));
} else {
Friends frnd = mFriends.get(index);
frnd.setStatus(message.getMessage());
mFriends.add(0, frnd);
mFriends.remove(index);
}
((ListAdapter) lvFriends.getAdapter()).notifyDataSetChanged();
}
I have an array string that i used in my Fragment,and i show the array string items with setListAdapter in my list:
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
String[] array = getResources().getStringArray(R.array.examlearray);
final ArrayList<String> str = new ArrayList<String>(Arrays.asList(array));
final ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(getActivity(),android.R.layout.simple_list_item_1, str );
setListAdapter(arrayAdapter);
final ListView listView = getListView();
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {...
and under my onActionItemClicked i want to implement my deleteSelectedItem() method,that delete selected list items,and this my code,but it didn't remove selected item,it is just remove from first of list,and when i select all the items and press remove,the app crash!what should do?,Any help would be appreciated! Thanks!
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// Respond to clicks on the actions in the CAB
switch (item.getItemId()) {
case R.id.delete:
// deleteSelectedItems();
Log.i(TAG, "deleteSelectedEntries");
SparseBooleanArray checkedItems = listView.getCheckedItemPositions();
for(int i=0;i<checkedItems.size();++i)
{ if(checkedItems.valueAt(i))
str.remove(i);
}
arrayAdapter.notifyDataSetChanged();
Instead of looping through the SparseBooleanArray you get from listView.getCheckedItemPositions(), you have to loop through the items of the ArrayAdapter and then check if the SparseBooleanArray returns true for this item. This is because when you remove items from the ArrayAdapter, listView.getCheckedItemPositions() still returns items that don't exist anymore.
So in your case:
SparseBooleanArray checkedItems = listView.getCheckedItemPositions();
// Loop backwards, so you can remove the items from within the loop
for (int i = arrayAdapter.getCount() - 1; i >= 0; i--) {
if (checkedItems.get(i)) {
// This item is checked and can be removed
arrayAdapter.remove(arrayAdapter.getItem(i));
}
}
The reason your app crashed is because you try to remove non existing items.
You will need to hold a reference to your array adapter. Then try calling the remove function on the adapter followed by notifyDataSetChanged. If you only have a reference to the position, you will also need to use getItem.
Keep the reference of adapter and data list used for creating adapter, Its better to use ArrayList, since it gives flexibility to remove elements easily. To set the adapter you can use following code -
String[] array = getResources().getStringArray(R.array.examlearray);
ArrayList<String> str = new ArrayList<String>(Arrays.asList(array));
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(getActivity(),android.R.layout.simple_list_item_1, str );
menuListView.setAdapter(arrayAdapter);
To remove element first remove the element from the data list and then calling notifyDatasetChnaged will update the listView as well. you can call following code inside onActionItemClicked
`for(int i=0;i<checkedItems.size();++i)
{ if(checkedItems.valueAt(i))
str.remove(i);
}
arrayAdapter.notifyDataSetChanged();
I'm using the following code to populate listview
Oncreate method:
SimpleAdapter adapter = new SimpleAdapter(this, list, R.layout.more_custom_row_view, new String[] {"title","desc"}, new int[] {R.id.text1,R.id.text2});
populateList();
setListAdapter(adapter);
static final ArrayList<HashMap<String,String>> list = new ArrayList<HashMap<String,String>>();
private void populateList() {
HashMap<String,String> temp = new HashMap<String,String>();
temp.put("title","Previous Searches");
temp.put("desc", "View your search history");
list.add(temp);
HashMap<String,String> temp1 = new HashMap<String,String>();
temp1.put("title","Settings");
temp1.put("desc", "Update your account settings");
list.add(temp1);
}
when i go to another activity and come back to this acitivity it duplicate list items
each time any one guide me what mistake am i doing?
The problem is that you are using a static list for your items and not clearing or reinstantiating it in your onCreate method.
The list will stay in memory as long as your program is running because it is defined static. If you are returning to the activity the items are again added to the list. You could use clear() to remove all items from the list in your populate list call before filling it again.
Simple, check adapter.isEmpty() before populating
This is a snippet from my own codebase.
private void populateWithAppPackages() {
//DON'T continue if the adapter is not empty; prevents duplicates
if(!mAdapter.isEmpty()){
return;
}
/* ... populate the list ... */
}
DON'T clear() the list adapter!!
clear() and repopulating list every time the activity or the fragment are visited is a very expensive set of operations. You would be better of intelligently populating the list as above.
removing final from the list resolved my problem.