4

I have a root recyclerview and each item is populated with this layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    >

        <TextView
            android:id="@+id/headerText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/accent_gradient"
            android:ellipsize="end"
            android:maxLines="1"
            android:padding="8dp"
            android:paddingStart="16dp"
            android:text="My original"
            android:textColor="@color/colorWhite"
            android:textSize="14sp" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rootRecycler"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        />
</LinearLayout>

As you can see, there is another recylcerview within each item. I wanted to achieve the effect of sectioned recyclerview with headers. (So Header > List Header > List)

Now each recyclerview is populated with this layout view:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    >
        <TextView
            android:layout_toStartOf="@id/relativeLayout3"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp"
            android:id="@+id/foodName_fv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingTop="8dp"
            android:text="Food name"
            android:ellipsize="end"
            android:maxLines="1"
            android:textColor="@color/colorBlack"
            android:textSize="18sp"
            android:transitionName="Food_Name"/>

        <TextView
            android:layout_below="@id/foodName_fv"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp"
            android:id="@+id/foodGrams_fv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingTop="8dp"
            android:paddingBottom="8dp"
            android:text="Grams"
            android:textColor="@color/colorLightGrey"
            android:textSize="15sp" />

    <LinearLayout
        android:id="@+id/relativeLayout3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:orientation="vertical"
        android:layout_alignParentEnd="true"
        >

        <TextView
            android:id="@+id/foodCalories_fv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:text="Calories"
            android:textColor="@color/colorBlack"
            android:textSize="18sp"
            android:transitionName="Food_Cal"/>

        <TextView
            android:layout_gravity="center"
            android:id="@+id/calText_fv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"

            android:paddingBottom="8dp"
            android:text="@string/calText_fv"
            android:textColor="@color/colorLightGrey"
            android:textSize="14sp" />
    </LinearLayout>

</RelativeLayout>

This is a fairly simple layout but for some odd reason the child recyclerview takes soo much time to inflate these rows. I did some testings and it took (on average) 1200 ms to load 300 rows!

Now the weirdest thing is that before this code i had a plain listview (without any sections and headers, just a big list) and it loaded all these rows very fast. I have'nt changed nothing but this code (Converting it from listview with an adapter to recyclerview with an adapter) so it looks pretty strange to me, but i dont know i might be missing something here.

This is the onCreateViewHolder method:

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

In conclusion the child recyclerview takes too much time to do this work. And yes I am aware i can do "Lazy loading" but I want to figure out why the hell is it so slow?

EDIT 1: Tried implementing it in a fresh new project, thought something with the broke somehow, but its still gave same results, also tried on my actual phone and not an emulator and still slow. I really want to figure out this one already because it seems very illogical.

EDIT 2: Posting the adapters code for hamza khan:

Code for the root recyclerview:

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

public class SelectFoodRootRecyclerAdapter extends RecyclerView.Adapter<SelectFoodRootRecyclerAdapter.ViewHolder> {

    private static final String TAG = "SelectFoodRootRecyclerAdapter";

    private Context mContext;
    private ArrayList<SectionedFoodGroup> items;
    private ArrayList<SelectFoodChildRecyclerAdapter> adapters; //For filter use outside this class

    RecyclerView.RecycledViewPool recycledViewPool;


    public SelectFoodRootRecyclerAdapter(Context mContext) {
        this.mContext = mContext;

        items = new ArrayList<>();
        adapters = new ArrayList<>();

        recycledViewPool = new RecyclerView.RecycledViewPool();

        SectionedFoodGroup section1 = new SectionedFoodGroup(mContext.getString(R.string.myFoods),
                FoodsDBHelper.getAllFoodRows(Food.DBType.USER_CREATED_FOODS_DB.ordinal()));

        SectionedFoodGroup section2 = new SectionedFoodGroup(mContext.getString(R.string.foods),
                FoodsDBHelper.getAllFoodRows(Food.DBType.REGULAR_FOOD_DB.ordinal()));

        addSection(section1);
        addSection(section2);
    }

    private void addSection(SectionedFoodGroup sectionedFoodGroup){
        if(sectionedFoodGroup.getFoods().size() > 0){
            items.add(sectionedFoodGroup);
            adapters.add(new SelectFoodChildRecyclerAdapter(mContext, sectionedFoodGroup.getFoods()));
        }
    }

    public ArrayList<SelectFoodChildRecyclerAdapter> getAdapters(){
        return adapters;
    }

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

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {

        SectionedFoodGroup sectionedFoodGroup = items.get(position);

        holder.headerText.setText(sectionedFoodGroup.getHeaderName());

        holder.foodsRecycler.setAdapter(adapters.get(position));
        holder.foodsRecycler.setRecycledViewPool(recycledViewPool);
    }

    @Override
    public int getItemCount() {
        return items != null ? items.size() : 0;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        TextView headerText;
        RecyclerView foodsRecycler;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            headerText = itemView.findViewById(R.id.headerText);
            foodsRecycler = itemView.findViewById(R.id.rootRecycler);
        }
    }
}

Code for the child recyclerview (the one containing all the actual rows):

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;

import java.text.DecimalFormat;
import java.util.ArrayList;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

public class SelectFoodChildRecyclerAdapter extends RecyclerView.Adapter<SelectFoodChildRecyclerAdapter.ViewHolder>{

    private static final String TAG = "SelectFoodChildRecyclerAdapter";

    private Context mContext;

    private ArrayList<Food> original;
    private DecimalFormat decimalFormat;

    public SelectFoodChildRecyclerAdapter(Context mContext, ArrayList<Food> foods) {
        this.mContext = mContext;
        this.original = foods;
        decimalFormat = new DecimalFormat("#.0");
    }

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

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int foodPos) {
        Food currFood = original.get(foodPos);

        holder.foodName.setText(currFood.getName());

        float grams = 100;
        if(currFood.getAmount() != 0) grams = (float)currFood.getAmount();

        int roundedGrams = (int)Math.round(grams);
        holder.foodGrams.setText(roundedGrams +" "+ mContext.getResources().getString(R.string.gramsWord));

        float caloriesToShow = (float)currFood.getCalories();

        holder.foodCalories.setText(decimalFormat.format(caloriesToShow));
    }

    @Override
    public int getItemCount() {
        return original != null ? original.size() : 0;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        TextView foodName;
        TextView foodGrams;
        TextView foodCalories;

        public ViewHolder(View itemView) {
            super(itemView);
            foodName = itemView.findViewById(R.id.foodName_fv);
            foodGrams = itemView.findViewById(R.id.foodGrams_fv);
            foodCalories = itemView.findViewById(R.id.foodCalories_fv);
        }
    }
}

The calling activity onCreate method:

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

        foodsRecyclerView = findViewById(R.id.rootRecycler);

        selectFoodRootRecyclerAdapter = new SelectFoodRootRecyclerAdapter(this);
        foodsRecyclerView.setAdapter(selectFoodRootRecyclerAdapter);

    }

Again, as you can see all the code is fairly simple..

EDIT 3: Ok so i've narrowed it down even more. I tried to do just one big recyclerview of the second layout and it worked fast. SOOO the problem must be with the first layout/the root recyclerview adapter code. but everything seems fine with it...

I really don't know what am I missing here!! Thanks for any kind of help!

Thanks!

Ofek
  • 324
  • 3
  • 13

1 Answers1

2

Well, because no one was able to answer my question and I've found my answer then ill share it with other people who might look at this in the future and think, wow this is exactly what I need.

So.. It turns out using a RecyclerView within RecyclerView is a big no no. thats because the onCreateViewHolder method gets called as many times as your items array size, and thats very heavy, especially if you have a large list. (I think its the same as RecyclerView within a ScrollView) - So try to avoid using this type of implementation.

So what can you do?

Option 1 - Create a multi view RecylcerView adapter. that means that you have one adapter and it can handle multiple view types (perfect for what i wanted. 1 view type is a header and the second view type is another layout to act as my main row).

You can look it up in google to find good results on how to implement it but the main So declare these variables:

private int VIEW_TYPE_HEADER = 0;
private int VIEW_TYPE_MAIN_ROW = 1;

Next override the next method, and what this basically does it gives each row a special int which we then can compare in onCreateViewHolder and decide which layout we want to inflate (e.g. If its going to be a header or a main row)

    @Override
    public int getItemViewType(int itemPos) {
        if(items.get(itemPos) instanceof String){
            return VIEW_TYPE_HEADER;
        }
        return VIEW_TYPE_MAIN_ROW;
    }

On the onCreateViewHolder we can test to see what type of row we are dealing with and inflate accordingly.

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        if(viewType == VIEW_TYPE_HEADER){
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.header)_layout, parent, false);
            return new HeaderViewHolder(view);
        }
//Else a regular row.
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.main_row, parent, false);
        return new MainViewHolder(view);
    }

And of course make a separate view holder for each type.

Option 2 - Because I need only 2-3 sections, what i could also do is just "hardcode" it like this:

Create a TextView to be the header and below it a RecyclerView AND again 2-3 times. this approach is obviously less flexible but it works if you want some small.

Hope this'll help someone one day.

Ofek
  • 324
  • 3
  • 13