I’ve managed to put together a custom BaseAdapter. This adapter is used to display three TextViews per list item. Works absolutely perfectly when there is one ListView within the activity.
However, I’m using a fragmented activity with 3 fragments – 2 of which contain a ListView which needs to use my custom adapter and XML.
When it comes to loading these two ListViews, firing the onItemClickListener on the first ListView loaded causes the following error:
11-20 18:02:15.295: E/AndroidRuntime(18563): 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. [in ListView(2131165192,
class android.widget.ListView) with
Adapter(class com.my.project.Adapter_CustomList)]
I don’t want the adapter to have changed – each ListView adapter is using a new Adapter_CustomList() so I’m not sure as to why they’re being referenced as the same adapter?
The adapter takes getActivity() as the context – since this is the same for both, is this causing the problem? Is there any way round this, or is this yet another drawback for fragments?
Custom Adapter is roughly as follows, I’ve taken some arbitrary chunks of code out…
public class Adapter_CustomList extends BaseAdapter {
private static ArrayList<Custom> results;
private LayoutInflater mInflater;
public Adapter_CustomList(Context context, ArrayList<Custom> res) {
results = res;
mInflater = LayoutInflater.from(context);
}
public int getCount() {
return results.size();
}
public Object getItem(int position) {
return results.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.custom_row_view, null);
holder = new ViewHolder();
//Set ViewHolder vars from inflated view
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
//Sets the TextViews
return convertView;
}
static class ViewHolder {
//Contains the TextView text vars
}
}
The activity is using a FragmentPagerAdapter (as generated by Eclipse):
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int i) {
Fragment fragment;
switch (i){
case 1:
fragment = new Frag_ListOne();
break;
case 2:
fragment = new Frag_ListTwo();
break;
default:
fragment = new Frag_Temporary();
break;
}
return fragment;
}
@Override
public int getCount() {
return 3;
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0: return getString(R.string.main_fragment1).toUpperCase();
case 1: return getString(R.string.main_fragment2).toUpperCase();
case 2: return getString(R.string.main_fragment3).toUpperCase();
}
return null;
}
}
Fragment example, both are almost identical but with unique layout files.
public class Frag_ListOne extends Fragment {
private ListView ListView;
private Adapter_CustomList listAdapter;
private Fetcher fetcher;
public Frag_ListOne(){}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
return inflater.inflate(R.layout.frag_list_one, container, false);
}
@Override
public void onStart(){
super.onStart();
ListView = (ListView) getActivity().findViewById(R.id.frag_one_list);
ListView.setEmptyView(getActivity().findViewById(R.id.frag_one_list_spinner));
if (listAdapter != null){
setListAdapter();
} else {
//This will always be called for now....
sendRequest();
}
}
private void sendRequest(){
fetcher = new Fetcher();
fetcher.execute("http://myapiurl.com");
}
private void cancelFetch(){
if (fetcher != null &&
(fetcher.getStatus() == AsyncTask.Status.PENDING || fetcher.getStatus() == AsyncTask.Status.RUNNING)){
fetcher.cancel(true);
}
}
private void displayError(String errMsg){
//Log errMsg
}
private void createListAdapter(ArrayList<Result> res){
listAdapter = new Adapter_CustomList(getActivity(), res);
setListAdapter();
}
private void setListAdapter(){
ListView.setAdapter(listAdapter);
ListView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> a, View v, int position, long id) {
Result r = (Result) ListView.getItemAtPosition(position);
Intent intent = new Intent(getActivity(), ViewResultActivity.class);
intent.putExtra(F_List.INTENT_VIEW_RESULT_OBJ, r);
startActivity(intent);
}
});
}
//Extended AsyncTask, essentially
public class Fetcher extends JSONRequest{
@Override
protected void onSuccess(JSONObject req, JSONObject res){
if (req != null && res != null){
try{
JSONArray jsons = res.getJSONArray("results");
ArrayList<Result> res = new ArrayList<Result>();
if (jsons.length() > 0){
for (int i=0;i<jsons.length();i++){
JSONObject jo = jsons.getJSONObject(i);
r = new Result();
r.setLineOne(jo.getInt("line1"));
r.setLineTwo(jo.getString("line2"));
r.setLineThree(jo.getString("line3"));
res.add(r);
}
createListAdapter(res);
} else {
//Nothing found
}
} catch (JSONException e){}
} else {
//Null received
}
}
@Override
protected void onError(String errMsg){}
@Override
protected void onCancel(){}
}
}
the adapter code looks ok – I’ve done something similar where I run two list views in separate fragments. (I’m making the assumption that results is actually equal to offerResults)
Maybe there’s an issue with the way you’re attaching to the list views?
Make sure that the Adapter and listViews are all being referenced in the fragments not the activity.
Check to see if you set the adapter and then change the list which drives it without calling adapter.notifyDataSetChanged()
Can be of more use with reference to the Fragments and the way the adapter is declared 🙂
Edit:
I was running a system where I would inflate 2 fragments into FrameLayouts using the FragmentManager instead of a FragmentPagerAdapter.
I’d double check to see if the fragments are being created and accessed correctly in the fragment pager adapter. You can do this by logging the fragmentId from getItem()
Also be careful with the way getItem(…) is written since that can lead to out of memory errors really quickly if you keep spawning new fragments. Instead I’d look at using the fragment manager and tags to manage the creation of new fragments.
Another thought is to consider setting up your lists inside sliding drawers so that they can pull them out as needed.
Edit 2:
try the following changes – this way the list lives in the fragment’s memory and isn’t part of the AsyncTask which might be GCed upon completion.