0
votes

(Tho i am working with Xamarin(C#), the codes or the structure should seem identical to Java.)

So, in an Activity, i have a ViewPager and a TabLayout. Inside the ViewPager, there's a Fragment, which contains a RecyclerView inside a NestedScrollView.

I have two different layouts and i want to display them in the RecyclerView. After doing some research, this is what i ended up with :

Created two ViewHolder(s)

public class PostRegularViewHolder : RecyclerView.ViewHolder
{
  public ImageView userImg { get; set; }
  public TextView userFullName { get; set; }

  public PostRegularViewHolder(View itemView) : base(itemView)
  {
   userImg = itemView.FindViewById<ImageView>(Resource.Id.feed_user_image);
   userFullName = itemView.FindViewById<TextView>(Resource.Id.username);
  }
}

public class CreatePostViewHolder : RecyclerView.ViewHolder
{
  public ImageView userImg { get; set; }
  public TextView userFirstName { get; set; }
  public CreatePostViewHolder(View itemView) : base(itemView)
  {
   userImg = itemView.FindViewById<ImageView>(Resource.Id.topBar_user_image);
   userFirstName = itemView.FindViewById<TextView>(Resource.Id.user_headerbar_title);
  }
}

Inside the Adapter, i have:

public override int ItemCount
{
 get
 {
   if (postCollection != null)
   {
     return postCollection.Count;
   }
   return 1; //Couldn't figure out what to return, so returned 1.
  }
}

public PostRegularAdapter(List<PostRegular> postList = null, string userFirstName = null)
{
 if (postList != null)
 {
  postCollection = postList;
 }
 UserFirstName = userFirstName;
}

public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
 if (postCollection == null)
 {
  RecyclerView.ViewHolder vh = new CreatePostViewHolder(LayoutInflater.From(parent.Context).Inflate(Resource.Layout.customview_user_writepostbar, parent, false));
  return vh;
 }

 else
 {
  RecyclerView.ViewHolder vh2 = new PostRegularViewHolder(LayoutInflater.From(parent.Context).Inflate(Resource.Layout.customview_postregular, parent, false));
  return vh2;
  }
}

Inside my Fragment, i have:

 public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
 {
  mainView = ///Inflated the mainview
  var layoutManager = new LinearLayoutManager(Context, LinearLayoutManager.Vertical, false);;
  recyclerView.SetLayoutManager(layoutManager);
  recyclerView.SetAdapter(postRegularAdapter);

  postRegularAdapter = new PostRegularAdapter(userFirstName: "Aousaf");         
  recyclerView.SetItemAnimator(new DefaultItemAnimator());
  return mainView;
 }

Here, in the OnCreateView method of the Fragment, i add the first view to the RecyclerView. To add the other view, i have this method inside the fragment :

public void PopulateWithPosts()
{
  List<PostRegular> posts = new List<PostRegular>();
  posts.Add(new PostRegular() { //passing the properties here });
  postRegularAdapter.postCollection = posts;
  postRegularAdapter.NotifyItemInserted(posts.Count - 1);
  postRegularAdapter.NotifyDataSetChanged();
  }
}

I call the PopulateWithPosts method from my activity, inside the OnTabSelected event of the TabLayout.

The problem here is that, the OnCreateViewHolder is not being called when i call the PopulateWithPosts method, or to narrow down, when i call the NotifyDataSetChanged method. What i can see using the debugger is that, OnCreateViewHolder is not called, rather, OnBindViewHolder is called.

The OnBindViewHolder method

 public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
 {
  if (postCollection == null)
  {
   CreatePostViewHolder vh2 = holder as CreatePostViewHolder;
   vh2.userFirstName.Text = UserFirstName + ", share something inspiring!";
  }

  else
  {
    PostRegularViewHolder vh = holder as PostRegularViewHolder;
    //Working with the viewHolder...
  }

According to the logic, the else block gets executed, however, as OnCreateViewHolder was not called this time, the holder remains CreatePostViewHolder aka the previous ViewHolder, which results in the cast failing, so i end up with vh being null.

I am a little puzzled here. Is there a way to call the OnCreateViewHolder when needed? Am i going in the right direction with adding multiple VIewHolders to a RecyclerView ? What am i missing here ?

1

1 Answers

1
votes

When RecyclerView has multiple ViewHolders, we usually override GetItemViewType method.

getItemViewType(int position)

This method's default implementation will always return 0, indicating that there is only 1 type of view. In your case, it is not so, and so you will need find a way to assert which row corresponds to which view type.

Besides,when we should notice the viewType parameter of following method:

public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)

According to the view type, we'll need to inflate the correct layout resource and create our view holder accordingly. The RecyclerView will handle recycling different view types in a way which avoids clashing of different view types.

For example: (assume you can match your viewholders with your object's field Type)

private  const int LAYOUT_ONE = 0;
private  const int LAYOUT_TWO = 1;

method GetItemViewType

public override int GetItemViewType(int position)
    {
        if (items[position].Type == 0)
            return LAYOUT_ONE;
        else
            return LAYOUT_TWO;
    }

method OnCreateViewHolder

public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
    {
        View view = null;
        switch (viewType)
        {
            case LAYOUT_ONE:
                view = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.customview_user_writepostbar, parent, false);
                return new CreatePostViewHolder(view);

            case LAYOUT_TWO:
                view = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.customview_postregular, parent, false);
                return new PostRegularViewHolder(view);

        }
    }

method OnBindViewHolder

public override void 
        OnBindViewHolder (RecyclerView.ViewHolder holder, int position)
    {
        int type = GetItemViewType(position);

        switch (type)
        {
            case LAYOUT_ONE:
                CreatePostViewHolder vh2 = holder as CreatePostViewHolder;
                vh2.userFirstName.Text = UserFirstName + ", share something inspiring!";
                break;
            case LAYOUT_TWO:
                PostRegularViewHolder vh = holder as PostRegularViewHolder;
                // other code
                break;
            default:
                break;
        }
    }