Stole51 Stole51 - 4 months ago 35
Android Question

ListView crash on SearchView

I am using BaseAdapter to populate quite large ListView (~150 songs) in SherlockFragment and it is working ok. The app crashes when I try to use SearchView. Not always, it happens randomly, sometimes on first text input, sometimes when I`m changing search text too fast. The message that I get is:


Java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread.


My fragment code:

public class Songs extends SherlockFragment {

private static final String KEY_CONTENT = "Songs:Content";

static final String KEY_ID = "id";
static final String KEY_NAME = "name";

Tost tost;
ListView list;
ArrayList<HashMap<String, String>> songsList;
DatabaseHandler db;
private String mContent = "???";

SongsAdapter adapter;
SearchView searchView;

public static Songs newInstance(String content) {
Songs fragment = new Songs();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 20; i++) {
builder.append(content).append(" ");
}
builder.deleteCharAt(builder.length() - 1);
fragment.mContent = builder.toString();

return fragment;
}


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if ((savedInstanceState != null)
&& savedInstanceState.containsKey(KEY_CONTENT)) {
mContent = savedInstanceState.getString(KEY_CONTENT);
}
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.songs_menu, menu);
MenuItem searchItem = menu.findItem(R.id.menu_search);

searchView = (SearchView) searchItem.getActionView();
searchView.setQueryHint("Search ...");

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {

@Override
public boolean onQueryTextChange(final String newText) {
if (newText.length() != 0) {
showFilteredItems(newText);
return true;
}
return false;
}

@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
});

searchView.setOnCloseListener(new SearchView.OnCloseListener() {
@Override
public boolean onClose() {
showFilteredItems("");
return false;
}
});

}

private void showFilteredItems( String query ) {
adapter.getFilter().filter(query);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

View v = inflater.inflate(R.layout.songs, container, false);
setHasOptionsMenu(true);
db = new DatabaseHandler(getActivity());

songsList = new ArrayList<HashMap<String, String>>();
list = (ListView) v.findViewById(R.id.lvSongs);
List<MSong> song = db.getAllSongs();
for (MSong cn : song) {

HashMap<String, String> map = new HashMap<String, String>();
map.put(KEY_ID, String.valueOf(cn.getID()));
map.put(KEY_NAME, cn.getName());
songsList.add(map);
}
adapter = new SongsAdapter((this), songsList);
list.setAdapter(adapter);
return v;

}


public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {

case android.R.id.home:
getActivity().finish();
return true;
}
return false;
}

@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(KEY_CONTENT, mContent);
}

}


and BaseAdapter:

public class SongsAdapter extends BaseAdapter implements Filterable {

private SherlockFragment fragment;
private ArrayList<HashMap<String, String>> data;
private static LayoutInflater inflater=null;
public ImageLoader imageLoader;

ArrayList<HashMap<String, String>> mDataShown;
ArrayList<HashMap<String, String>> mAllData;

public SongsAdapter(SherlockFragment frag, ArrayList<HashMap<String, String>> d) {
fragment = frag;
data=d;
inflater = (LayoutInflater)fragment.getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader=new ImageLoader(fragment.getActivity().getApplicationContext());
mDataShown= (ArrayList<HashMap<String, String>>) d;
mAllData = (ArrayList<HashMap<String, String>>) mDataShown.clone();
}

public int getCount() {
return mDataShown.size();
}

public Object getItem(int position) {
return data.get(position);
}

public long getItemId(int position) {
return position;
}

public View getView(int position, View convertView, ViewGroup parent) {
View vi=convertView;
if(convertView==null)
vi = inflater.inflate(R.layout.songs_row, null);
TextView tvname = (TextView)vi.findViewById(R.id.tvName);

HashMap<String, String> song = new HashMap<String, String>();
song = data.get(position);

tvname.setText(song.get(Songs.KEY_NAME));
return vi;
}

public Filter getFilter() {
Filter nameFilter = new Filter() {

@Override
protected FilterResults performFiltering(CharSequence s) {

if(s != null)
{
ArrayList<HashMap<String, String>> tmpAllData = mAllData;
ArrayList<HashMap<String, String>> tmpDataShown = mDataShown;
tmpDataShown.clear();
for(int i = 0; i < tmpAllData.size(); i++)
{
if(tmpAllData.get(i).get(Songs.KEY_NAME).toLowerCase().startsWith(s.toString().toLowerCase()))
{
tmpDataShown.add(tmpAllData.get(i));
}
}

FilterResults filterResults = new FilterResults();
filterResults.values = tmpDataShown;
filterResults.count = tmpDataShown.size();
return filterResults;
}
else
{
return new FilterResults();

}
}

@Override
protected void publishResults(CharSequence s, FilterResults results) {
if(results != null && results.count > 0)
{
notifyDataSetChanged();
}
}};
return nameFilter;
}


}


Is anyone have some idea why is this happening?

1Up 1Up
Answer

Two possibilities: 1: Use view.post(Runnable r) to ensure this method is called on the same thread of the listView:

private void showFilteredItems( String query ) {
        list.post(new Runnable(){
            public void run()
            {
                adapter.getFilter().filter(query);
            }
        });
    }

2: Maybe you need to call adapter.notifyDataSetChanged();

private void showFilteredItems( String query ) {
    adapter.getFilter().filter(query);
    adapter.notifyDataSetChanged();
}