Fragment with Listview with different types of rows, where the row is a compound view

98 Views Asked by At

I'm doing on a questionnaire which means that one fragment is representing one question. A question contains some fields which can be of different types, e.g. text field, rated field...

These fields are rendered as rows in ListView in the fragment. They are implemented as compound views because I need doing some other works with fields.

The problem is when I rendering the rows by the custom adapter, the InflateException exception is thrown:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cz.rusna.clinical_questionnaire, PID: 9141
    android.view.InflateException: <merge /> can be used only with a valid ViewGroup root and attachToRoot=true
        at android.view.LayoutInflater.inflate(LayoutInflater.java:475)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:414)
        at cz.rusna.clinical_questionnaire.views.adapters.TestRunnerAdapter.getView(TestRunnerAdapter.java:80)
        at android.widget.AbsListView.obtainView(AbsListView.java:2347)
        at android.widget.ListView.makeAndAddView(ListView.java:1864)
        at android.widget.ListView.fillDown(ListView.java:698)
        at android.widget.ListView.fillFromTop(ListView.java:759)
        at android.widget.ListView.layoutChildren(ListView.java:1673)
        at android.widget.AbsListView.onLayout(AbsListView.java:2151)
        at android.view.View.layout(View.java:15671)
        at android.view.ViewGroup.layout(ViewGroup.java:5038)
        at android.support.constraint.ConstraintLayout.onLayout(ConstraintLayout.java:1915)
        at android.view.View.layout(View.java:15671)
        at android.view.ViewGroup.layout(ViewGroup.java:5038)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:579)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:514)
        at android.view.View.layout(View.java:15671)
        at android.view.ViewGroup.layout(ViewGroup.java:5038)
        at android.support.v7.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:443)
        at android.view.View.layout(View.java:15671)
        at android.view.ViewGroup.layout(ViewGroup.java:5038)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:579)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:514)
        at android.view.View.layout(View.java:15671)
        at android.view.ViewGroup.layout(ViewGroup.java:5038)
        at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1703)
        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1557)
        at android.widget.LinearLayout.onLayout(LinearLayout.java:1466)
        at android.view.View.layout(View.java:15671)
        at android.view.ViewGroup.layout(ViewGroup.java:5038)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:579)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:514)
        at android.view.View.layout(View.java:15671)
        at android.view.ViewGroup.layout(ViewGroup.java:5038)
        at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2086)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1843)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1061)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5885)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:767)
        at android.view.Choreographer.doCallbacks(Choreographer.java:580)
        at android.view.Choreographer.doFrame(Choreographer.java:550)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:753)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:135)
        at android.app.ActivityThread.main(ActivityThread.java:5254)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

The activity xms looks like:

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_runner_test"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".views.activities.TestRunnerActivity">

    <FrameLayout
        android:id="@+id/test_runner_frame_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</android.support.constraint.ConstraintLayout>

The fragment layout looks like:

<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/fragment_test_runner"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".views.fragments.TestRunnerFragment">

</ListView>

The fragment onCreateView looks like:

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        ListView listView = (ListView) inflater.inflate(R.layout.fragment_test_runner, container, false);
        listView.setAdapter(new TestRunnerAdapter(mCurrent.getQuestionFields(), getContext()));

        return listView;
    }

The adapter code looks like:

public class TestRunnerAdapter extends BaseAdapter {

    private List<QuestionField> mQuestionFields;

    private Context mContext;

    public TestRunnerAdapter(List<QuestionField> fields, Context context) {
        mQuestionFields = fields;
        mContext = context;
    }


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

    @Override
    public QuestionField getItem(int position) {
        return mQuestionFields.get(position);
    }

    @Override
    public int getItemViewType(int position) {

        QuestionField questionField = mQuestionFields.get(position);

        if (questionField.getFieldType() == FieldType.TEXT) {
            return 0;
        } else if (questionField.getFieldType() == FieldType.RATED) {
            return 1;
        } else {
            return 2;
        }
    }

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

    @Override
    public int getViewTypeCount() {
        return 3;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        View view = convertView;
        int type = getItemViewType(position);
        QuestionField questionField = mQuestionFields.get(position);
        if (view == null) {
            LayoutInflater inflater = LayoutInflater.from(mContext);

            if (type == 0) {
                view = inflater.inflate(R.layout.text_field, parent, false);
                TextFieldData textFieldData = (TextFieldData) questionField.getFieldData();
                ((TextFieldView)view).setTextField(textFieldData.getText());
            } else if (type == 1) {
                view = inflater.inflate(R.layout.rated_field, parent, false);
                RatedFieldData ratedFieldData = (RatedFieldData) questionField.getFieldData();
                ((RatedFieldView)view).setRatedField(ratedFieldData);
            } else {
                view = inflater.inflate(R.layout.unrated_field, parent, false);
            }
        }

        return view;
    }
}

The example of a compound view.

public class TextFieldView extends LinearLayout {

    private TextView mTextView;

    public TextFieldView(Context context) {
        this(context, null);
    }

    public TextFieldView(Context context, AttributeSet attributes) {
        this(context, attributes, 0);
    }

    public TextFieldView(Context context, AttributeSet attributes, int defStyleAttr) {
        super(context, attributes, defStyleAttr);
        init(context);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public TextFieldView(Context context, AttributeSet attributes, int defStyleAttr, int defStyleRes) {
        super(context, attributes, defStyleAttr, defStyleRes);
        init(context);
    }

    private void init(Context context) {
        LayoutInflater.from(context).inflate(R.layout.text_field, this);

        mTextView = (TextView) findViewById(R.id.text);
    }

    public void setTextField(String text) {
        mTextView.setText(text);
    }
}

And its xml:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="18dp"
        android:gravity="center_vertical" />

</merge>

The code is failing in the getView method in the adapter class when it rendering these views on the line:

view = inflater.inflate(R.layout.text_field, parent, false);
0

There are 0 best solutions below