'Can I make a recyclerview without an xml layout for the ViewHolder?

I'm putting a RecyclerView in my android app. So, like in all the documentation I've seen for recycler views, I have an Adapter class with a ViewHolder class defined inside it. That code is shown below (in PointChangeAdapter.java).

However, there is one difference between my code and most of the tutorials I've seen; I do not have an xml file defining a layout for my ViewHolder. At least that's what I'm trying to get working.

It seems I shouldn't need to define a custom layout, because the elements of my RecyclerView are just TextViews. This could probably be done with a ListView, but I like the flexibility of RecyclerView, and also just want to understand if it is possible to get what I have here working. That is, with the recycler and without the xml layout.

To accommodate the lack of xml layout, I have modified my Adapter's onCreateViewHolder method. Where a typical example would use a layout inflater, e.g.

    @Override
    public PointChangeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final TextView textView = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.my_viewholder_xml_layout, parent, false);
        return new PointChangeViewHolder(textView);
    }

I instead have

    @Override
    public PointChangeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final TextView textView = new TextView(parent.getContext());
        parent.addView(textView);
        return new PointChangeViewHolder(textView);
    }

That is, instead of calling inflate on an xml layout and specifying the RecyclerView parent, I create a new TextView and add it directly to the RecyclerView parent via addView.

Only problem is this does not work.

It gives me a "ViewHolder views must not be attached when created" error:

E/AndroidRuntime: FATAL EXCEPTION: main Process: com.mherzl.pointcounter, PID: 5253 java.lang.IllegalStateException: ViewHolder views must not be attached when created. Ensure that you are not passing 'true' to the attachToRoot parameter of LayoutInflater.inflate(..., boolean attachToRoot) at androidx.recyclerview.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:7080)

...

I have also tried removing the parent.addView(textView); line entirely, making my example almost identical to this example, which, like I am trying to do, does not specify an xml layout. When I run my code without the addView line, I do not get the error, but in that case the text still does not show up in the recycler views. So something appears wrong then as well. Also, I feel that the addView line should be needed; since the inflater appears to attach the TextView to the RecyclerView parent, it seems that what I replace the inflater with should attach to it as well.

Is it possible to make a RecyclerView using TextViews as elements instead of an element defined by a custom xml layout?

Here is my code:

PointChangeAdapter.java:

package com.mherzl.pointcounter;

import android.view.ViewGroup;
import android.widget.TextView;

import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class PointChangeAdapter extends RecyclerView.Adapter<PointChangeAdapter.PointChangeViewHolder> {

    private List<Integer> recentChanges;

    public PointChangeAdapter(List<Integer> recentChanges) {
        this.recentChanges = recentChanges;
    }

    @Override
    public int getItemCount() {
        if (recentChanges == null) {
            return 0;
        }
        return recentChanges.size();
    }

    @Override
    public PointChangeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final TextView textView = new TextView(parent.getContext());
        parent.addView(textView);
        return new PointChangeViewHolder(textView);
    }

    @Override
    public void onBindViewHolder(PointChangeViewHolder holder, int position) {
        final int pointChange = recentChanges.get(position);
        String text = Integer.toString(pointChange);
        holder.setText(text);
    }

    public static class PointChangeViewHolder extends RecyclerView.ViewHolder {
        private TextView textView;

        public PointChangeViewHolder(TextView textView) {
            super(textView);
            this.textView = textView;
        }

        public void setText(String text) {
            textView.setText(text);
        }
    }
}

And here's what runs it -- in the onCreateView of the fragment using the RecyclerView:

RecyclerView pointChangeRecycler =
        (RecyclerView) view.findViewById(R.id.point_changes_recycler);
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
pointChangeRecycler.setLayoutManager(layoutManager);
List<Integer> myList = new ArrayList<>();
myList.add(1); myList.add(2); myList.add(3);
PointChangeAdapter pointChangeAdapter = new PointChangeAdapter(myList);
pointChangeRecycler.setAdapter(pointChangeAdapter);


Solution 1:[1]

Yes. onCreateViewHolder just returns a ViewHolder with a ViewGroup (possibly even just a View) as the root view.

Your problem is that you added it to a parent. You don't want to do that. The RecyclerView will end up being its parent. You should just be creating it and returning a holder with that view as the root view.

Yes, even though the parent passed in is probably (although not necessarily) also the RecyclerView. The layout manager of the recycler view does the adding. That's why the last parameter of your inflate() call is false- to prevent inflate from adding it to a parent.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Gabe Sechan