2

In RecyclerView.ViewHolder , a view is passed to the constructor. This view is an inflated layout. The RecyclerView.ViewHolder only bind the views with findViewById.

The RecyclerView.Adapter has the role to :

  • inflate the layout in onCreateViewHolder
  • bind the ViewHolder with the data set with onBindViewHolder

I have several RecyclerViews displaying the same list of data. I want each RecyclerView to display differently with their respective ViewHolder. My goal is to use the same generic RecyclerView.Adapter for each RecyclerView. So the ViewHolder has to be passed to this RecyclerView.Adapter. I'm trying to implement a ViewHolder that can implement all 3 methods. Any idea how to achieve this ?

I looked at different projects. The best so far, AdapterDelegates circumvents this problem. But you still have to deal with AdapterDelegate and ViewHolder classes . How to combine both in the same class ? ( without inner class )

Raymond Chenon
  • 11,482
  • 15
  • 77
  • 110
  • Can you explain what this means "My vision is to pass any customizable ViewHolder without having to create one RecyclerView.Adapter per ViewHolder"? – lionscribe Aug 31 '16 at 11:56
  • In simple words : pass any ViewHolder to a generic RecyclerView.Adapter. I have several RecyclerView displaying the same list of data . But each RecyclerView displays with a different ViewHolder. I don't want to create again another Adapter. Adapters have boilerplate code. Hope it is clearer. I will re-edit the question if you understand this comment – Raymond Chenon Aug 31 '16 at 12:08
  • With some thought I was now able to figure out. You should definitely edit your question. – lionscribe Aug 31 '16 at 12:21

3 Answers3

2

As promised I have created a new answer, which does not use reflection. It technically uses two classes (a factory class and a holder class), not one, but is just the same. The code is tested, and it works.

MyAdapter.java

public class MyAdapter extends RecyclerView.Adapter<MyViewHolder>
{
    List<Object> _data;
    MyViewHolder.Factory _factory;

    MyAdapter(List data, MyViewHolder.Factory factory)
    {
        _data = data;
        _factory = factory;
        if (_data == null || _factory == null)
        {
            throw new NullPointerException("Both data and factory cannot be null!");
        }
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
    {
        return _factory.createViewHolder(parent, viewType);
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position)
    {
        holder.bindViewHolder(_data.get(position));
    }

    @Override
    public int getItemCount()
    {
        return _data.size();
    }
}

MyViewHolder.java

public abstract class MyViewHolder extends RecyclerView.ViewHolder
{
    public interface Factory
    {
        public abstract MyViewHolder createViewHolder(ViewGroup parent, int viewType);
    }

    public MyViewHolder(View itemView)
    {
        super(itemView);
    }

    public abstract void bindViewHolder(Object data);
}

MainActivity.java

public class MainActivity extends AppCompatActivity
{

    static class NameViewHolder extends MyViewHolder
    {
        // If preferred, the following can be created anonymously in code
        static class Factory implements MyViewHolder.Factory
        {
            @Override
            public MyViewHolder createViewHolder(ViewGroup parent, int viewType)
            {
                View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.main_recycler_item, parent, false);
                return new NameViewHolder(v);
            }
        };

        TextView tv;
        NameViewHolder(View v)
        {
            super(v);
            tv = (TextView)v.findViewById(R.id.textView);
        }

        @Override
        public void bindViewHolder(Object data)
        {
            tv.setText((String)data);
        }
    }


    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View view)
            {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        RecyclerView rv = (RecyclerView)findViewById(R.id.my_recycler_view);
        rv.setHasFixedSize(true);

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        rv.setLayoutManager(layoutManager);

        ArrayList<String> data = new ArrayList();
        for (int i = 1; i < 100; ++i)
            data.add(Integer.toString(1000 + i));

        MyAdapter adapter = new MyAdapter(data, new NameViewHolder.Factory());
        rv.setAdapter(adapter);
    }

}
lionscribe
  • 3,413
  • 1
  • 16
  • 21
  • Thanks a lot. I have yet to find a better alternative than **ViewHolder.Factory**. However I don't know about posting 2 answers in the same question. – Raymond Chenon Sep 05 '16 at 22:00
1

I would suggest creating an abstract ViewHolder parent Class. It should have a static instantate method and a bindViewHolder method. Design the Adapter constructor to accept the ViewHolder parent Class object. When used, pass the child Class Object, and in onCreateViewHolder, use Reflection to create the child ViewHolder. When you get an onBindViewHolder, just pass it to the ViewHolder.

Here is a working example. I tested it, and it worked. I have removed non-essential code.

MainActivity.java

public class MainActivity extends AppCompatActivity
{

    static class NameViewHolder extends MyViewHolder
    {
        TextView tv;
        public static MyViewHolder instantate(ViewGroup parent, int viewType)
        {
            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.main_recycler_item, parent, false);
            MyViewHolder vh = new NameViewHolder(v);
            return vh;
        }
        NameViewHolder(View v)
        {
            super(v);
            tv = (TextView)v.findViewById(R.id.textView);
        }

        @Override
        public void bindViewHolder(Object data)
        {
            tv.setText((String)data);
        }
    }


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

        RecyclerView rv = (RecyclerView)findViewById(R.id.my_recycler_view);
        rv.setHasFixedSize(true);

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        rv.setLayoutManager(layoutManager);

        ArrayList<String> data = new ArrayList();
        for (int i = 1; i < 100; ++i)
            data.add(Integer.toString(1000 + i));
        MyAdapter adapter = new MyAdapter(data, NameViewHolder.class);
        rv.setAdapter(adapter);
    }
}

MyViewHolder.java

public abstract class MyViewHolder extends RecyclerView.ViewHolder
{
    // The following has to be declared in sub class. As Java 7 does not support static interface, we commented it out here
    //public static MyViewHolder instantate(ViewGroup parent, int viewType);

    public MyViewHolder(View itemView)
    {
        super(itemView);
    }
    public abstract void bindViewHolder(Object data);
}

MyAdapter.java

public class MyAdapter extends RecyclerView.Adapter<MyViewHolder>
{
    List<Object> _data;
    Class _holderClass;

    MyAdapter(List data, Class holderClass)
    {
        _data = data;
        _holderClass = holderClass;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
    {
        MyViewHolder vh = null;
        try
        {
            Class[] cArg = {ViewGroup.class, int.class};
            Method instantateMethod = _holderClass.getMethod("instantate", cArg);
            vh = (MyViewHolder) instantateMethod.invoke(null, parent, viewType);
        }
        catch (NoSuchMethodException e)
        {
            e.printStackTrace();
        }
        catch (InvocationTargetException e)
        {
            e.printStackTrace();
        }
        catch (IllegalAccessException e)
        {
            e.printStackTrace();
        }
        return vh;
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position)
    {
        holder.bindViewHolder(_data.get(position));
    }

    @Override
    public int getItemCount()
    {
        return _data.size();
    }

}
lionscribe
  • 3,413
  • 1
  • 16
  • 21
  • In the Adapter's onCreateViewHolder , you mean `_holderClass.getDeclaredConstructor(ViewGroup.class, int.class).newInstance(parent,viewType);` . Because you need a `Context` ( from the viewGroup ) to inflate. My first idea was a similar solution as yours. Try with a working example. Further long the way, you will run into problems . I cannot list them all here. – Raymond Chenon Aug 31 '16 at 23:19
  • Correct. I don't need this functionality, so I am not planning to try... – lionscribe Sep 01 '16 at 01:57
  • Lol , so you submitted an unchecked answer. At least, I upvoted for a try – Raymond Chenon Sep 01 '16 at 08:03
  • I specifically said I did not test it. As for your saying that it will run in to problems, without listing them, well that is not too helpful, as in theory this code should work. – lionscribe Sep 01 '16 at 11:50
  • Here are some errors caught, after solving them one by one, you get newer errors. - `The specified child already has a parent. You must call removeView() on the child's parent first` - `Attempt to write to field 'int android.support.v7.widget.RecyclerView$ViewHolder.mItemViewType' on a null object reference at android.view.ViewGroup.resetResolvedLayoutDirection(ViewGroup.java:5713)` ... Sorry the formatting on comment is not great. – Raymond Chenon Sep 01 '16 at 11:57
  • Cannot think of any reason you should get that. – lionscribe Sep 01 '16 at 12:17
  • @RaymondChenon Well, just to spite you :-), I have edited my answer and added a working example. It works, with NO errors! – lionscribe Sep 02 '16 at 16:51
  • @lionscribre , thanks for the solution. But it relies on the assumption, all child classes from `MyViewHolder` will have a static "instantate" ( correctly spelled ). I'm pretty sure the someone will file a bug because he/she hasn't read Javadoc. I know abstract method cannot be static. So I am tempted to think there is a better alternative . And I am still looking for it, so not the correct answer for now. If I could vote twice ... – Raymond Chenon Sep 04 '16 at 16:17
  • 1
    I actually have a new idea, using Factory. I will work on it tomorrow. – lionscribe Sep 04 '16 at 16:18
  • I have a vague idea what you mean. It will look like this class https://github.com/sockeqwe/AdapterDelegates/blob/master/app/src/main/java/com/hannesdorfmann/adapterdelegates2/sample/ReptilesAdapter.java – Raymond Chenon Sep 04 '16 at 16:21
  • No. Will look similar to what have now, but rather than passing MyViewHolder class, with a static instantate method that need reflection, it will pass a MyViewHolder.Factory object, with a single method that will return a MyViewHolder object. – lionscribe Sep 04 '16 at 16:26
  • I am writing it as **library** module. Your classes `MyViewHolder` and `MyAdapter` are contained in the **library** module. But the activity is in the **sample project**. Just make sure your `MyViewHolder.Factory` will be in the **library** module ( without any dependency to sample ). Or just put the Factory in the **sample** – Raymond Chenon Sep 04 '16 at 16:37
  • I have just posted a new answer, that should resolve your issues. – lionscribe Sep 05 '16 at 16:51
  • Thanks I will try in a few hours. Really appreciate your help. – Raymond Chenon Sep 05 '16 at 16:58
0

You can by making passing R.layout to your RVadapter

static int layoutResource;

public RVadapter(Context context, int id) {
    layoutResource = id;
}

then you can do this in your ViewHolder

if(RVadapter.layoutResource == R.layout.message_layout) {
            message_view = (CardView) itemView.findViewById(R.id.card_message);
            message_image = (ImageView) itemView.findViewById(R.id.message_image);
            message_name = (TextView) itemView.findViewById(R.id.messenger);
            message_details = (TextView) itemView.findViewById(R.id.details);
        }
        else if(RVadapter.layoutResource == R.layout.guide_layout){
            guide_name = (TextView) itemView.findViewById(R.id.new_travel_name);
            guide_gendr_age = (TextView) itemView.findViewById(R.id.new_travel_gender_age);
            guide_tours = (TextView) itemView.findViewById(R.id.new_travel_tour);
            rb = (RatingBar) itemView.findViewById(R.id.new_travel_rb);
        }