Search code examples
javaandroidlistviewbaseadapter

Listview show different types of rows


I have a ListView and a BaseAdapter. In the Adapter I show 7 different types of views.

This is image is showing when I run the project all the ListView and the view inside are loading correctly. But when I scroll the list view down I get an error. Show all good

All shows well when I run the project, but the problem goes when I scroll down the ListView, and throw me the error:

E/AndroidRuntime: FATAL EXCEPTION: main
                  Process: com.panelapps.mapen, PID: 25969
                  java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
                      at com.panelapps.uiformcreator.UIFormAdapter.getView(UIFormAdapter.java:234)
                      at android.widget.AbsListView.obtainView(AbsListView.java:2413)
                      at android.widget.ListView.makeAndAddView(ListView.java:1975)
                      at android.widget.ListView.fillDown(ListView.java:709)
                      at android.widget.ListView.fillGap(ListView.java:673)
                      at android.widget.AbsListView.trackMotionScroll(AbsListView.java:5475)
                      at android.widget.ListView.trackMotionScroll(ListView.java:1894)
                      at android.widget.AbsListView.scrollIfNeeded(AbsListView.java:3708)
                      at android.widget.AbsListView.onTouchMove(AbsListView.java:4157)
                      at android.widget.AbsListView.onTouchEvent(AbsListView.java:3964)
                      at android.view.View.dispatchTouchEvent(View.java:9957)
                      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2705)
                      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2386)
                      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2711)
                      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2400)
                      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2711)
                      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2400)
                      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2711)
                      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2400)
                      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2711)
                      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2400)
                      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2711)
                      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2400)
                      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2711)
                      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2400)
                      at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:416)
                      at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1834)
                      at android.app.Activity.dispatchTouchEvent(Activity.java:3154)
                      at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:68)
                      at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:378)
                      at android.view.View.dispatchPointerEvent(View.java:10177)
                      at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4650)
                      at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4518)
                      at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3969)
                      at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4022)
                      at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3988)
                      at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3996)
                      at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3969)
                      at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4022)
                      at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3988)
                      at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4117)
                      at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3996)
                      at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4174)
                      at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3969)
                      at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4022)
                      at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3988)
                      at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3996)
                      at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3969)
                      at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6459)
                      at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6433)
                      at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6394)
E/AndroidRuntime:     at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6593)
                      at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
                      at android.view.InputEventReceiver.nativeConsumeBatchedInputEvents(Native Method)
                      at android.view.InputEventReceiver.consumeBatchedInputEvents(InputEventReceiver.java:176)
                      at android.view.ViewRootImpl.doConsumeBatchedInput(ViewRootImpl.java:6541)
                      at android.view.ViewRootImpl$ConsumeBatchedInputRunnable.run(ViewRootImpl.java:6616)
                      at android.view.Choreographer$CallbackRecord.run(Choreographer.java:871)
                      at android.view.Choreographer.doCallbacks(Choreographer.java:683)
                      at android.view.Choreographer.doFrame(Choreographer.java:613)
                      at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:857)
                      at android.os.Handler.handleCallback(Handler.java:751)
                      at android.os.Handler.dispatchMessage(Handler.java:95)
                      at android.os.Looper.loop(Looper.java:154)
                      at android.app.ActivityThread.main(ActivityThread.java:6316)
                      at java.lang.reflect.Method.invoke(Native Method)
                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:872)
                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:762)

My BaseAdapter code is:

public class UIFormAdapter extends BaseAdapter {
    CallbackFormAdapter callback;

    public interface CallbackFormAdapter {
        void getData();
    }

    Context context;
    ArrayList<Form> UIForm = new ArrayList<>();
    private LayoutInflater inflater;

    //functino
    Function function = new Function();

    public UIFormAdapter(Context context, ArrayList<Section> form, CallbackFormAdapter callback) {
        this.context = context;
        this.callback = callback;

        //do the form
        for (int i = 0; i<form.size(); i++){
            //add the separator
            UIForm.add(new Form("", form.get(i).getText(), 0));

            //go over the forms inside
            for (int j = 0; j<form.get(i).getForms().size(); j++){
                UIForm.add(form.get(i).getForms().get(j));
            }
        }
    }

    @Override
    public int getCount() {
        return UIForm.size();
    }

    @Override
    public Object getItem(int position) {
        return UIForm.get(position);
    }

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

    @Override
    public int getItemViewType(int position) {
        return UIForm.get(position).getType();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        //holder
        Holder viewHolder = null;

        //get the data
        Form data = UIForm.get(position);

        //if inflater inicialize
        if (inflater == null)
            inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        if (convertView == null){
            //get the tipe
            int type = getItemViewType(position);

            //switch type
            switch (type){
                case 0:
                    convertView = inflater.inflate(R.layout.separator, parent, false);
                    //set the holder
                    viewHolder = new Holder(convertView, type);

                    viewHolder.txtSection.setText(data.getText());
                    break;
                case Types.EditText:
                    convertView = inflater.inflate(R.layout.text_edit, parent, false);
                    //set the holder
                    viewHolder = new Holder(convertView, type);

                    viewHolder.etEditText.setHint(data.getText());
                    break;
                case Types.Single_Spinner:
                    convertView = inflater.inflate(R.layout.single_spinner, parent, false);
                    //set the holder
                    viewHolder = new Holder(convertView, type);

                    viewHolder.txtSingleSpinner.setText(data.getText());
                    break;
                case Types.Spinner:
                    convertView = inflater.inflate(R.layout.spinner, parent, false);
                    //set the holder
                    viewHolder = new Holder(convertView, type);

                    viewHolder.spinner.setAdapter(data.getAdapter());
                    viewHolder.spinner.setBackgroundColor(Color.LTGRAY);
                    break;
                case Types.Date:
                    convertView = inflater.inflate(R.layout.button, parent, false);
                    //set the holder
                    viewHolder = new Holder(convertView, type);

                    viewHolder.button.setText(data.getText());
                    viewHolder.button.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            Calendar mcurrentDate = Calendar.getInstance();
                            final int[] mYear = {mcurrentDate.get(Calendar.YEAR)};
                            final int[] mMonth = {mcurrentDate.get(Calendar.MONTH)};
                            final int[] mDay = {mcurrentDate.get(Calendar.DAY_OF_MONTH)};

                            DatePickerDialog mDatePicker = new DatePickerDialog(context, new DatePickerDialog.OnDateSetListener() {
                                public void onDateSet(DatePicker datepicker, int selectedyear, int selectedmonth, int selectedday) {
                                    Calendar myCalendar = Calendar.getInstance();
                                    myCalendar.set(Calendar.YEAR, selectedyear);
                                    myCalendar.set(Calendar.MONTH, selectedmonth);
                                    myCalendar.set(Calendar.DAY_OF_MONTH, selectedday);

                                    //date
                                    String myDate = function.dateToString(myCalendar.getTime(), "dd/MM/yyyy");
                                    //viewHolder.button.setText(myDate);

                                    mDay[0] = selectedday;
                                    mMonth[0] = selectedmonth;
                                    mYear[0] = selectedyear;
                                }
                            }, mYear[0], mMonth[0], mDay[0]);
                            //mDatePicker.setTitle("Select date");
                            mDatePicker.show();
                        }
                    });
                    break;
                case Types.Button:
                    convertView = inflater.inflate(R.layout.button, parent, false);
                    //set the holder
                    viewHolder = new Holder(convertView, type);

                    viewHolder.button.setText(data.getText());
                    viewHolder.button.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            callback.getData();
                        }
                    });
                    break;
            }

            //set the holder
            convertView.setTag(viewHolder);

        }else{ //else customview null

            //get the viewHolder
            viewHolder = (Holder)convertView.getTag();

            //get the tipe
            int type = UIForm.get(position).getType();

            switch (type){
                case 0:
                    viewHolder.txtSection.setText(data.getText());
                    break;
                case Types.EditText:
                    viewHolder.etEditText.setHint(data.getText());
                    break;
                case Types.Single_Spinner:
                    viewHolder.txtSingleSpinner.setText(data.getText());
                    break;
                case Types.Spinner:
                    viewHolder.spinner.setAdapter(data.getAdapter());
                    viewHolder.spinner.setBackgroundColor(Color.LTGRAY);
                    break;
                case Types.Date:
                    viewHolder.button.setText(data.getText());
                    viewHolder.button.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            Calendar mcurrentDate = Calendar.getInstance();
                            final int[] mYear = {mcurrentDate.get(Calendar.YEAR)};
                            final int[] mMonth = {mcurrentDate.get(Calendar.MONTH)};
                            final int[] mDay = {mcurrentDate.get(Calendar.DAY_OF_MONTH)};

                            DatePickerDialog mDatePicker = new DatePickerDialog(context, new DatePickerDialog.OnDateSetListener() {
                                public void onDateSet(DatePicker datepicker, int selectedyear, int selectedmonth, int selectedday) {
                                    Calendar myCalendar = Calendar.getInstance();
                                    myCalendar.set(Calendar.YEAR, selectedyear);
                                    myCalendar.set(Calendar.MONTH, selectedmonth);
                                    myCalendar.set(Calendar.DAY_OF_MONTH, selectedday);

                                    //date
                                    String myDate = function.dateToString(myCalendar.getTime(), "dd/MM/yyyy");
                                    //viewHolder.button.setText(myDate);

                                    mDay[0] = selectedday;
                                    mMonth[0] = selectedmonth;
                                    mYear[0] = selectedyear;
                                }
                            }, mYear[0], mMonth[0], mDay[0]);
                            //mDatePicker.setTitle("Select date");
                            mDatePicker.show();
                        }
                    });
                    break;
                case Types.Button:
                    viewHolder.button.setText(data.getText());
                    viewHolder.button.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            callback.getData();
                        }
                    });
                    break;
            }
        }

        //return the view
        return convertView;
    }

    //CREATE THE HOLDERS
    private class Holder{
        EditText etEditText;
        TextView txtSection;
        MaterialSpinner spinner;
        TextView txtSingleSpinner;
        Button button;
        public Holder(View v, int type) {
            switch (type){
                case Types.EditText:
                    this.etEditText = v.findViewById(R.id.etEditText);
                    break;
                case Types.Single_Spinner:
                    this.txtSingleSpinner = v.findViewById(R.id.txtSingleSpinner);
                    break;
                case 0:
                    this.txtSection = v.findViewById(R.id.txtSeparator);
                    break;
                case Types.Spinner:
                    this.spinner = v.findViewById(R.id.spinner);
                    break;
                case Types.Button:
                    this.button = v.findViewById(R.id.button);
                    break;
                case Types.Date:
                    this.button = v.findViewById(R.id.button);
                    break;
            }

        }
    }
}

The error is showing me is in the next line (viewHolder.button.setText ...):

case Types.Button:
                    viewHolder.button.setText(data.getText());

And this button is the next one to show in the ListView.


Solution

  • You need to override getViewTypeCount to indicate the number of different types of views, otherwise when it tries to reuse a view it may be of a different type (so convertView isn't null, but the ViewHolder is of the wrong type). It worked for the first screen of views since convertView would be null for all of them.

    @Override
    public int getViewTypeCount() {
        return 6; // question says 7 view types but I counted 6
    }
    

    See here for some more details.