Search code examples
androidlistviewcustom-adapter

Android ListView with Custom Adapter shows only last item


I am using a ListView to show some data that comes from a JSON. The JSON has 3 objects. I am using a Custom Adapter for the list view.

Here is the layout of list view

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/doc_signing"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">
    <ListView android:id="@+id/android:list"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:dividerHeight="1.5dp" />
    <TextView android:id="@+id/android:empty"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="22sp"
        android:text="@string/no_docs_to_sign"/>
</LinearLayout>

Here is the layout of the list view row

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:id="@+id/doc_list_row"
    android:orientation="vertical" >
    <TextView
        android:id="@+id/doc_name"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:textSize="22sp" >
    </TextView>
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView
            android:id="@+id/doc_id"
            android:layout_weight="0.50"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:paddingBottom="2dp"
            android:paddingLeft="5dp"
            android:paddingRight="3dp"
            android:textSize="16sp" >
        </TextView>
        <TextView
            android:id="@+id/doc_date"
            android:layout_weight="0.50"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:paddingBottom="2dp"
            android:paddingLeft="3dp"
            android:paddingRight="3dp"
            android:textSize="16sp" >
        </TextView>
    </LinearLayout>
</LinearLayout>

Code to read the JSON and set the adapter is here

ArrayList<DocInfo> docInfo = new ArrayList<DocInfo>();
try {
    JSONArray jArray = new JSONArray(readData());
    for (int i = 0; i < jArray.length(); i++) {
        JSONObject jObject = jArray.getJSONObject(i);
        Log.i("Object" + i, jObject.toString());
        docInfo.add(new DocInfo(Long.valueOf(jObject.getString("doc_id")), jObject.getString("doc_name"), jObject.getString("doc_file_name"), jObject.getString("doc_date")));
    }
} catch (Exception e) {
}
CustomAdapter adapter = new CustomAdapter(this, R.layout.doc_list, docInfo);
setListAdapter(adapter);

The custom adapter

public class CustomAdapter extends ArrayAdapter<DocInfo> {
    private List<DocInfo> entries;
    private Activity activity;

    public CustomAdapter(Activity a, int textViewResourceId, ArrayList<DocInfo> entries) {
        super(a, textViewResourceId, entries);
        this.entries = entries;
        this.activity = a;
    }

    public static class ViewHolder{
        public TextView tv_doc_name;
        public TextView tv_doc_id;
        public TextView tv_doc_date;
    }

    @Override
    public int getCount(){
          return entries!=null ? entries.size() : 0;
    }

    @Override
    public DocInfo getItem(int index) {
        return entries.get(index);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            LayoutInflater vi = LayoutInflater.from(activity);
            convertView = vi.inflate(R.layout.doc_list, (ViewGroup) activity.findViewById(R.id.doc_list_row));
            holder = new ViewHolder();
            holder.tv_doc_name = (TextView) convertView.findViewById(R.id.doc_name);
            holder.tv_doc_id = (TextView) convertView.findViewById(R.id.doc_id);
            holder.tv_doc_date = (TextView) convertView.findViewById(R.id.doc_date);
            convertView.setTag(holder);
        }
        else
            holder=(ViewHolder) convertView.getTag();

        final DocInfo custom = entries.get(position);
        if (custom != null) {
            holder.tv_doc_name.setText(custom.get_doc_name());
            holder.tv_doc_id.setText(String.valueOf(custom.get_doc_id()));
            holder.tv_doc_date.setText(custom.get_doc_date());
        }
        Log.i("Custom" + position, custom.get_doc_name() + String.valueOf(custom.get_doc_id()) + custom.get_doc_date());
        return convertView;
    }
}

The JSON being used for testing

[
    {
        "doc_name": "Home Loan Agreement",
        "doc_file_name": "home_loan_agreement.pdf",
        "doc_id": "6781",
        "doc_date": "11-Mar-2017"
    },
    {
        "doc_name": "Personal Loan Agreement",
        "doc_file_name": "personal_loan_agreement.pdf",
        "doc_id": "2517",
        "doc_date": "19-Mar-2017"
    },
    {
        "doc_name": "Insurance Policy Proposal",
        "doc_file_name": "policy_proposal.pdf",
        "doc_id": "1291",
        "doc_date": "24-Mar-2017"
    }
]

Finally, the output showing only the last object of the JSON in the listview at the bottom of the page screen shot

When debugging, I see that the JSON has been read correctly and all 3 objects are present in the array list. The GetView method of the custom adapter is called for the 3 objects and the view is being formed and returned correctly. Tried different things for a long time but couldn't figure out where the first two items are why they are not showing up on the screen.

Any help will be greatly appreciated.


Solution

  • You're inflating your list item layout incorrectly.

    convertView = vi.inflate(R.layout.doc_list, (ViewGroup) activity.findViewById(R.id.doc_list_row));
    

    In that particular inflate() overload, the second argument is the ViewGroup that the inflated View is added to after inflation. The first time your getView() method runs, activity.findViewById(R.id.doc_list_row) will return null, since a doc_list_row View doesn't exist in the hierarchy yet, so the inflated View will just get added to the ListView like normal, after the return. The next time getView() runs, however, there is a doc_list_row View, from the first list item, so the second item's View, and any ones inflated after that, get added inside the first item, stacking below its original child Views in its vertical LinearLayout.

    Furthermore, the inflate() method returns the root View of whatever results from the inflation and (optional) addition. If the ViewGroup argument is null, there is nothing to add the inflated layout to, and it returns the root View of the just inflated layout. If the ViewGroup argument is not null, then that ViewGroup is returned as the root, since it is now the top parent. After your first list item is created, the getView() method is returning the same first item View repeatedly, and the ListView has problems reconciling the layout with its own internal logic, which is why the one item you do see isn't aligned to the top.

    The correct way to inflate a list item is to pass the ViewGroup parent parameter as the second argument in the inflate() call, and false as a third argument, to indicate that the inflated layout should not be added to the parent there, since it will eventually be added to the ListView after the return.

    convertView = vi.inflate(R.layout.doc_list, parent, false);
    

    You may also want to make the layout_height of the root LinearLayout in the doc_list layout wrap_content, if just to be on the safe side. ListView should be smart enough to override that and wrap it instead, but it's best not to introduce anything potentially problematic.