6

i'm new to android, I've been working on a project, and in my news feeds page, I'm trying to include a modular feed RecyclerView, which shows a question with different answer forms, varrying according to the Question type. The way I was doing it so far was by using the include and turning the forms visible when needed. recently since i added more modules, the app started to slowdown segnificantly, so i'm trying to implement ViewStubs.

This is my RecyclerView adapter:

public class ReQuestionAdapter extends RecyclerView.Adapter<FeedItem> {
private ArrayList<Question> myQuestions;
public ReQuestionAdapter(Context context, ArrayList<Question> questions) {
    myQuestions = questions ;
}

@Override
public FeedItem onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.list_item_re_question, parent, false);
    return new FeedItem(view);
}

@Override
public void onBindViewHolder(FeedItem holder, int position) {
    Question q = myQuestions.get(position);
    holder.bindQuestion(q);
}

@Override
public int getItemViewType(int position) {
    return 0;
}

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

And this is the ViewHolder class for the adapter:

public class FeedItem extends RecyclerView.ViewHolder{
private Question mQuestion;
public TextView tvName;
public TextView tvTime;
public TextView tvContent;
public ProfilePictureView profilePictureView;
public ViewStub moduleView;
private int moduleType;

public FeedItem(View itemView) {
    super(itemView);

}

public void bindQuestion(Question question) {
    mQuestion = question;
    tvTime = (TextView) itemView.findViewById(R.id.li_q_date);
    tvContent = (TextView) itemView.findViewById(R.id.li_q_content);
    moduleView = (ViewStub) itemView.findViewById(R.id.module_viewstub);
    tvTime.setText(TimeHandler.When(mQuestion.publish_time));
    tvContent.setText(mQuestion.content);
    moduleType = question.type;
        switch (moduleType) {
            case Question.TYPE_YN:
                moduleView.setLayoutResource(R.layout.module_yes_no);
                moduleView.inflate();
                break;
            case Question.TYPE_CUSTOM:
                moduleView.setLayoutResource(R.layout.module_custom);
                moduleView.inflate();
                break;
            default:
                break;
        }
    }
}

Now, the problem is that the ViewStub which contains a certain layout, cannot be reinflated with a new one, the reason for that is that it gets removed from the view hirarchy as soon as it leaves the screen, the symptoms: When scrolling down the RecyclerView, the first list items that fill the screen are working perfect, but others to load when the previous leave the screen cause the FeedItem binding to bring a NullPointerException. (It canno't find it in the list item layout).

I'm looking for a solution as efficiant as ViewStubs, or a way to make them work properly, since I got many modules and inflating them all in each item as invisible would make my app slow.

Valentin Montmirail
  • 2,594
  • 1
  • 25
  • 53
Or Posener
  • 93
  • 1
  • 5

1 Answers1

7

In your bindQuestion() method you are referencing two different layouts to inflate, so in essence you have two different view types.

Adapter views have an efficient way way to handle this built right in.

Start by overriding getItemViewType(). When the item at position gets the module_yes_no layout, return 0. When it gets the module_custom layout, return 1.

Then in onCreateViewHolder(), when the viewType parameter is 0, inflate a list_item_re_question view complete with the module_yes_no layout. When viewType == 1, inflate the module_custom version of the view.

Now when you get a view in onBindViewHolder(), it will already have the correct subview, so you proceed to fill out that view as needed. By using getItemViewType(), the RecyclerView is working with you to recycle the exact view you need.

You can even have two FeedItem subclasses, one for module_yes_no and one for module_custom, so in onBindViewHolder(), you just check the class of the ViewHolder and branch accordingly.

That should help improve the performance of your app.

kris larson
  • 30,387
  • 5
  • 62
  • 74
  • Thx alot, so i get that using two different FeedItem classes, the recyclerview would know by itself which parts to preserve and which are different? or will it create 2 cycling views repeating according to the request? – Or Posener Jul 24 '15 at 12:17
  • 1
    The two `FeedItem` classes are just for your benefit. `RecyclerView` knows which view to recycle for you by virtue of you overriding `getItemViewType()`. So let's say item 20 is a yes_no. If you have set up `getItemViewType()` to return 0 for yes_no items, then when user scrolls to item 20, `RecyclerView` calls your `getItemViewType()` with position == 20. It receives 0, and says, "Oh look, I have a type 0 view for recycling right here", and passes that view to `onBindViewHolder()`. Make sense? – kris larson Jul 24 '15 at 12:41
  • Then if `RecyclerView` doesn't have a view available to recycle, it calls `onCreateViewHolder()` with `viewType` == 0 so you know which type of view to inflate and which type of view holder to create. – kris larson Jul 24 '15 at 12:48
  • Got it, thank you very much sir you really helped me :P I was also wandring if i should be using a question display layout and a answer form layout, and include them together to different layouts which i'll use for the inflation: – Or Posener Jul 24 '15 at 12:54
  • or is there an easier way to do that, like implamenting the part by code somehow (simillarly to viewstub) – Or Posener Jul 24 '15 at 12:57
  • You can do that a few different ways. You could even have two different versions of the question layout fully defined without using includes (that would be my choice). That part isn't as critical. Your app gets the performance benefit because your recycled views are fully inflated and you don't have to inflate something every single time the user scrolls. I think you should try a couple different ways and see if one seems to make scrolling a little more snappy. – kris larson Jul 24 '15 at 13:13
  • @kris larson I have a similar use case where I need to select one of two different layouts for a RecyclerView that inflates (only) a single CardView. If the CardView has a small amount of data from the Room database then use a "small" layout. If the CardView has a large amount of data from the database then use an expanded layout. Would your recommendation to override getItemViewType() work in this case? If so, are both set up or do I need to use an "if (small data)" {layout 1}, else {layout 2}? I would appreciate any small example you could provide. – AJW Dec 06 '19 at 04:48
  • 1
    @AJW Yes, you should override `getItemViewType()`. It's very simple: Step 1: Override `getItemViewType()` so it returns 0 for small data, 1 for large data. Step 2: Override `onCreateViewHolder()` with a switch on the `viewType` parameter: 0 inflates small layout, 1 inflates expanded layout. Step 3: In `onBindViewHolder()`, your view holder parameter will *already* have the correct layout inflated for your data by virtue of step 1 and step 2. If you're stuck, post a new question so the community has a chance to answer. If you link to your question in a comment here I will take a look at it. – kris larson Dec 07 '19 at 09:16