0

So, I want to add Headers between rows in my ListView. I am doing so by simply inflating a different layout for a header. When the ListView first loads, it seems fine. But when I scroll, I am getting a null pointer on this line at random times:

tvHeader.setText(location.headerText);

This only happens when I am adding Headers to the ListView; if I only add rows, it is fine. Any help is appreciated, thanks.

Here is my code:

Here is my Location object

package com.example.listview;


public class Location {
    public String name;
    public String details;
    public String distance;
    public String hours;
    public boolean header = false;
    public String headerText;

    public Location(String name, String details, String distance) {
        this.name = name;
        this.details = details;
        this.distance = distance;
        header = false;
    }

    public Location(String headerText) {
        this.headerText = headerText;
        header = true;
    }
}

And here is the Adapter for the ListView:

package com.example.listview;

import java.util.ArrayList;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class LocationAdapter extends ArrayAdapter<Location> {

    public LocationAdapter(Context context, ArrayList<Location> locations) {
        super(context, R.layout.item_location, locations);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // Get the data item for this position
        final Location location = getItem(position);

        if (!location.header) { // if it is NOT a header
            if (convertView == null) {
                convertView = LayoutInflater.from(getContext()).inflate(
                        R.layout.item_location, parent, false); // inflate the row xml
            }

            TextView tvName = (TextView) convertView.findViewById(R.id.tvName);
            TextView tvDetails = (TextView) convertView
                    .findViewById(R.id.tvDetails);
            TextView tvDistance = (TextView) convertView
                    .findViewById(R.id.tvDistance);

            // Populate the data into the template view using the data object
            tvName.setText(location.name);
            tvDetails.setText(location.details);
            tvDistance.setText(location.distance);

        } else if (location.header) {
            if (convertView == null) {
                convertView = LayoutInflater.from(getContext()).inflate(
                        R.layout.listview_header, parent, false); // inflate the header xml
            }

            TextView tvHeader = (TextView) convertView
                    .findViewById(R.id.tvHeader);

            tvHeader.setText(location.headerText);

        }
        return convertView;
    }
}

Here is the item_location.xml for a row in the ListView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_layout"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:listSelector="@drawable/listview_selector"
    android:minHeight="140dip"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/tvName"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="25sp" />

    <TextView
        android:id="@+id/tvDetails"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="15sp" />

    <TextView
        android:id="@+id/tvDistance"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="15sp" />

</LinearLayout>

Here is the listview_header.xml for the Header:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_layout"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:listSelector="@drawable/listview_selector">

    <TextView
        android:id="@+id/tvHeader"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="25sp" />

</RelativeLayout>

And finally, here is my main activity where I am adding rows and headers:

package com.example.listview;

import java.util.ArrayList;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;

public class MainActivity extends Activity {

    LocationAdapter adapter;
    ArrayList<Location> arrayOfLocations;
    ListView listView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = (ListView) findViewById(R.id.listView1);

        arrayOfLocations = new ArrayList<Location>();

        for (int i = 0; i < 10; i++) {
            arrayOfLocations.add(new Location("August 9th")); // add a header
            arrayOfLocations.add(new Location("Best Fruit Stand Ever",
                    "Fruit!", "2 miles")); // add a row
        }

        // Create the adapter to convert the array to views
        adapter = new LocationAdapter(this, arrayOfLocations);

        listView.setAdapter(adapter);
    }
}

and the Logcat:

12-29 17:36:22.091: E/AndroidRuntime(4930): java.lang.NullPointerException
12-29 17:36:22.091: E/AndroidRuntime(4930):     at com.example.listview.LocationAdapter.getView(LocationAdapter.java:53)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.widget.AbsListView.obtainView(AbsListView.java:2161)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.widget.ListView.makeAndAddView(ListView.java:1840)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.widget.ListView.fillDown(ListView.java:675)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.widget.ListView.fillGap(ListView.java:639)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.widget.AbsListView.trackMotionScroll(AbsListView.java:4970)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.widget.AbsListView.scrollIfNeeded(AbsListView.java:3126)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.widget.AbsListView.onTouchEvent(AbsListView.java:3400)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.View.dispatchTouchEvent(View.java:7391)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2205)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1940)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2211)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1954)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2211)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1954)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2211)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1954)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2211)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1954)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:2228)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1471)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.app.Activity.dispatchTouchEvent(Activity.java:2424)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2176)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.View.dispatchPointerEvent(View.java:7571)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:3883)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:3778)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3379)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3429)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3398)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3483)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3406)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:3540)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3379)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3429)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3398)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3406)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3379)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:5419)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:5399)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:5370)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:5493)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:182)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.InputEventReceiver.nativeConsumeBatchedInputEvents(Native Method)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.InputEventReceiver.consumeBatchedInputEvents(InputEventReceiver.java:174)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl.doConsumeBatchedInput(ViewRootImpl.java:5472)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.ViewRootImpl$ConsumeBatchedInputRunnable.run(ViewRootImpl.java:5512)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.Choreographer$CallbackRecord.run(Choreographer.java:749)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.Choreographer.doCallbacks(Choreographer.java:562)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.Choreographer.doFrame(Choreographer.java:530)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.os.Handler.handleCallback(Handler.java:730)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.os.Handler.dispatchMessage(Handler.java:92)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.os.Looper.loop(Looper.java:137)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at android.app.ActivityThread.main(ActivityThread.java:5289)
12-29 17:36:22.091: E/AndroidRuntime(4930):     at java.lang.reflect.Method.invok
Harry
  • 772
  • 10
  • 32
  • Would be good to know to which line the line 53 corresponds on the code here. – mthandr Dec 30 '14 at 00:09
  • I put it at the top: tvHeader.setText(location.headerText); – Harry Dec 30 '14 at 00:46
  • The null pointer is from the TextView tvHeader – Harry Dec 30 '14 at 00:46
  • I see. Did you check which one of the things are null? If the String or the TextView? – mthandr Dec 30 '14 at 01:37
  • It is not null, it works for some of the rows and not for others. For example, if I try and catch for a null pointer, some will have the header text and some will be blank and it will atch the exception. I'm clueless as to why it works sometimes and not others. – Harry Dec 30 '14 at 01:41
  • That is for the String, I mean. I checked and the TextView is what is null, not the string – Harry Dec 30 '14 at 01:42

1 Answers1

1

When you use convertViews, it means you are trying to recycle views. Imagine that when you get inside the getView and the location is a header.

It will check for the convertView and see it is not null, since it was recycled and it is from a row that is not a header.

Now it will try to get the R.id.tvHeader from the convertView, but there is no R.id.tvHeader because the layout inflated was the item_location layout.

If it is not null, you will have to check if the convertView is the inflated listview_header or change around how you are dealing with the rows.

mthandr
  • 3,062
  • 2
  • 23
  • 33
  • Awesome, I'll try to work around this, thanks a lot! – Harry Dec 30 '14 at 02:23
  • Do you have any recommendations on how to "check if the convertView is the inflated listview_header" ? – Harry Dec 30 '14 at 04:50
  • Nope, I have just removed the check to see if convertView is null and it works but I'm assuming this is bad practice as it won't recycle the views anymore – Harry Dec 30 '14 at 14:47
  • 1
    Take a look at getViewTypeCount which returns the number of types of layouts present in the list and also getItemViewType. In this answer you can look at that: http://stackoverflow.com/questions/4777272/android-listview-with-different-layout-for-each-row. Also consider using ViewHolder for holding the views so you can then call getTag to attribute it to convertView so you don't have to call findViewById every time because it is CPU intensive. – mthandr Dec 30 '14 at 15:09