javaandroidrestnestedrecyclerviewexpandablerecyclerview

Struggling with expandable nested recyclerview


Im working an app. It fetches lists(country and cities lists) from API using retrofit. On my main activity i have a rv which is the main rv and also displays the country list. my country list items have a sub rv that displays the cities of the country list. Everything is fine so far. but what i wanna do here is to display the list of the cities when i expand the country list item. when i run my app and expand a random country list item and just scroll down i see some of the country list items also expanded while i didnt. what am i doing wrong?

here my codes:

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

    private ArrayList<CountryList> countryArrayList;

    public CountryRvAdapter(ArrayList<CountryList> countryArrayList) {
        this.countryArrayList = countryArrayList;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        CountryBinding binding = CountryBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
        return new ViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        CountryList countryList = countryArrayList.get(position);
        holder.binding.countryName.setText(countryList.getCountry());

        CityRvAdapter cityRvAdapter = new CityRvAdapter(countryList.getCitiesList());
        holder.binding.cityListRv.setAdapter(cityRvAdapter);

        holder.binding.expandCountryButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                RecyclerView cityListRv= holder.binding.cityListRv;
                if (cityListRv.getVisibility() == View.GONE)
                    cityListRv.setVisibility(View.VISIBLE);
                else
                    cityListRv.setVisibility(View.GONE);

                cityRvAdapter.notifyDataSetChanged();
            }
        });

    }

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

    public class ViewHolder extends RecyclerView.ViewHolder {

        private CountryBinding binding;

        public ViewHolder(CountryBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }
    }
}


Solution

  • When using recyclerview, the views that contain the data are recycled. This means that if the country list item in the first view is expanded, it will remain expanded for the next data it will contain. That is why you're seeing that behavior. You have to find a way to save each view's state so that even if the views are recycled, they will display them according to the changes the user has made.

    I can think of one way you can fix this. You can give your CountryList object a new property we can call expanded. This property is a boolean object that we can get and set. So in your CountryList class, put this:

    class CountryList {
        // Initial state is false;
        Boolean expanded = false;
    
        public Boolean isExpanded(){
            return expanded;
        }
    
        public void setExpanded(Boolean expanded){
            this.expanded = expanded;
        }
    }
    

    Now, on your adapter, we have to check if the state is expanded. If it's expanded, then let's show the cityListRv. If it's not, let's hide it. We'll do the checking in onBindViewHolder method. Also, when the expand button is pressed, don't forget to set the new value for the expanded property.

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        CountryList countryList = countryArrayList.get(position);
        holder.binding.countryName.setText(countryList.getCountry());
     
        // Set adapter for cityListRv
        RecyclerView cityListRv = holder.binding.cityListRv;
        CityRvAdapter cityRvAdapter = new CityRvAdapter(countryList.getCitiesList());
        cityListRv.setAdapter(cityRvAdapter);
     
        // Check if it's expanded or not and update the view
        if (countryList.isExpanded()){
            // It's expanded so show the cityListRv
            cityListRv.setVisibility(View.VISIBLE);
        } else {
            // It's not expanded so hide the cityListRv
            cityListRv.setVisibility(View.GONE);
        }
    
        holder.binding.expandCountryButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (cityListRv.getVisibility() == View.GONE) {
                    cityListRv.setVisibility(View.VISIBLE);
                    countryList.setExpanded(true);
                } else {
                    cityListRv.setVisibility(View.GONE);
                    countryList.setExpanded(false);
                }
    
                cityRvAdapter.notifyDataSetChanged();
            }
        });
    }
    

    This should be working now.