Search code examples
androidandroid-recyclerviewpojo

Show values of same key in one TextView in Recyclerview


I have the following JSON which I'm parsing in GSON and Retrofit. I want to display the values of the same id in one TextView. What's happening right now is that all the values are added to the array and they are being displayed in separate TextViews. I want to show all values which have the same id to be displayed in one TextView. For eg. id: 240 ab values should be in one TextView. Currently, all ab values are in separate TextView.

This is how the data is currently displaying:

enter image description here

This is how I want the data to be: enter image description here

JSON::

{
  "abc": {
    "1": {
      "1": {
        "ab": "some content",
        "id": "240",
        "key": "value"
      },
      "2": {
        "ab": "some content",
        "id": "240",
        "key": "value"
      },
      "3": {
        "ab": "some content",
        "id": "240",
        "key": "value"
      }
    },
    "2": {
      "1": {
        "ab": "more content",
        "id": "241",
        "key": "value"
      },
      "2": {
        "ab": "more content 1",
        "id": "241",
        "key": "value"
      },
      "3": {
        "ab": "more content 2",
        "id": "241",
        "key": "value"
      }
    }
  }
}

POJOContent::

public class POJOContent {

    @SerializedName("ab")
    public String content;

    @SerializedName("id")
    public String id;

    @SerializedName("key")
    public String key;

    @Override
    public String toString() {
        return content;
    }

    //getters and setters

}

MyContentWrapper::

public class MyContentWrapper {
    public Map<Integer, MyMap> abc;
}

MyMap::

public class MyMap extends HashMap<Integer, POJOContent> {
    @Override
    public POJOContent put(Integer key, POJOContent value) {
        if(null==value.getContent() || value.getContent().isBlank()) {
            return null;
        }
        // Added only if content = "ab" is not blank.
        return super.put(key, value);
    }
}

Callback:

    Callback<MyContentWrapper> myCallback = new Callback<MyContentWrapper>() {
                @Override
                public void onResponse(Call<MyContentWrapper> call, Response<MyContentWrapper> response) {
                    if (response.isSuccessful()) {
                        Log.d("Callback", " Message: " + response.raw());
                        Log.d("Callback", " Message: " + response.body().abc.values());

                        MyContentWrapper contentWrapper = response.body();

                        List<POJOContent> pojo = new ArrayList<>();

                        for (Map.Entry<Integer, MyMap> entry : contentWrapper.abc.entrySet()) {
                            Integer key = entry.getKey();
                            MyMap map = entry.getValue();
                            if (!map.isEmpty()){
                                Log.d("Callback", " Key: " + key);
                                Log.d("Callback", " Value: " + map.values());

                                pojo.addAll(map.values());
                            }
                        }

                        MyContentViewAdapter adapter = new MyContentViewAdapter(pojo);
                        recyclerView.setAdapter(adapter);
                    } else {
                        Log.d("Callback", "Code: " + response.code() + " Message: " + response.message());
                    }
                }
                @Override
                public void onFailure(Call<MyContentWrapper> call, Throwable t) {
                    t.printStackTrace();
                }
            };

RecyclerAdapter::

                public class MyContentViewAdapter extends RecyclerView.Adapter<MyContentViewAdapter.ViewHolder> {
    private List<POJOContent> data;
    private MyClickListener clickListener;


    public class ViewHolder extends RecyclerView.ViewHolder {
        public TextView text;
        private LinearLayout itemLayout;

        public ViewHolder(View v) {
            super(v);
            text = (TextView) v.findViewById(R.id.text_content);
        }
    }

    public MyContentViewAdapter(List<POJOContent> data) {
        this.data = data;
        Log.d("Recyclerview Data", data.toString());
    }


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

    @Override
    public void onBindViewHolder(MyContentViewAdapter.ViewHolder holder, int position) {
        POJOContent pojo = data.get(position);
        Log.d("Recyclerview", pojo.getContent());
        holder.text.setText(pojo.getContent());
        holder.itemView.setTag(pojo.getContent());
    }

    public void setOnItemClickListener(MyClickListener clickListener) {
        this.clickListener = clickListener;
    }

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

}

Solution

  • EDIT:

    I added nested RecyclerView inside ViewHolder, so the content and value fields will be displayed dynamically. I'm Adding full code of 2 Adapter and 2 ViewHolder classes, 2 xml layouts and screenshot

    I'm pretty sure it will run really smoothly with very large list too.

    Everything under ID(240,241) is another recyclerView.

    enter image description here

    The idea is that list's size, for adapter to populate itself, should be as many as the number of distinct ids, so that only that many Viewholders are inflated.

     List<List<POJOContent>> listOfPojoLists = new ArrayList<>();
    
        for (Map.Entry<Integer, MyMap> entry : contentWrapper.abc.entrySet()) {
            Integer key = entry.getKey();
            MyMap map = entry.getValue();
            if (!map.isEmpty()){
                Log.d("Callback", " Key: " + key);
                Log.d("Callback", " Value: " + map.values());
    
                listOfPojoLists.add(new ArrayList<>(map.values()));
            }
        }
    
        MyContentViewAdapter adapter = new MyContentViewAdapter(listOfPojoLists);
        recyclerView.setAdapter(adapter);
    

    MyContentViewAdapter.java

    public class MyContentViewAdapter extends RecyclerView.Adapter<MyContentViewAdapter.ViewHolder> {
    private List<List<POJOContent>> data;
    private MyClickListener clickListener;
    
    
    MyContentViewAdapter(List<List<POJOContent>> data) {
        this.data = data;
    }
    
    @Override
    public MyContentViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.fragment_content_card, parent, false);
        return new MyContentViewAdapter.ViewHolder(v);
    }
    
    @Override
    public void onBindViewHolder(MyContentViewAdapter.ViewHolder holder, int position) {
        holder.bind(data.get(position));
    }
    
    public void setOnItemClickListener(MyClickListener clickListener) {
        this.clickListener = clickListener;
    }
    
    @Override
    public int getItemCount() {
        return data.size();
    }
    
    
    class ViewHolder extends RecyclerView.ViewHolder {
    
        private TextView textId;
        private InnerListAdapter innerAdapter;
    
        // inside constructor we are initializing inner recyclerView and inner Adapter.
        // there will only be 3 - 5 instances of them ever created(using this 
        // particular viewHolder layouts), no more.
        // might be slightly more if u use layouts with lower height
        ViewHolder(View v) {
            super(v);
            textId =  v.findViewById(R.id.tv_Id);
            RecyclerView innerRecycler = v.findViewById(R.id.rv_inner_list);
            // I added DividerItemDecoration so it would be clear that there are actually different viewHolders
            // displayed by recyclerView
            innerRecycler.addItemDecoration(new DividerItemDecoration(v.getContext(), DividerItemDecoration.VERTICAL));
            innerAdapter = new InnerListAdapter();
            innerRecycler.setAdapter(innerAdapter);
        }
    
        /* We just submit new list for our inner adapter
        so it will handle rebinding values to its viewHolders */
        void bind(List<POJOContent> pojoList){
            textId.setText(pojoList.get(0).id);
            innerAdapter.setNewItems(pojoList);
        }
    }
    

    }


    InnerListAdapter.java

    public class InnerListAdapter extends RecyclerView.Adapter<InnerListAdapter.InnerViewHolder> {
    
    private List<POJOContent> items = new ArrayList<>();
    
    @NonNull
    @Override
    public InnerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new InnerViewHolder(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_inner_list, parent, false));
    }
    
    @Override
    public void onBindViewHolder(@NonNull InnerViewHolder holder, int position) {
        holder.bind(items.get(position));
    }
    
    @Override
    public int getItemCount() {
        return items.size();
    }
    
    void setNewItems(List<POJOContent> newItems){
        items.clear();
        items.addAll(newItems);
        notifyDataSetChanged();
    }
    
    class InnerViewHolder extends RecyclerView.ViewHolder{
        TextView tv_value;
        TextView tv_content;
    
        InnerViewHolder(@NonNull View itemView) {
            super(itemView);
            tv_value = itemView.findViewById(R.id.tv_value);
            tv_content = itemView.findViewById(R.id.tv_content);
        }
    
        void bind(POJOContent pojoContent){
            tv_value.setText(pojoContent.getKey());
            tv_content.setText(pojoContent.getContent());
        }
    }
    

    }


    fragment_content_card.xml layout for main recyclerView viewholder

        <?xml version="1.0" encoding="utf-8"?>
       <androidx.cardview.widget.CardView 
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        android:padding="8dp">
    
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <TextView
                android:id="@+id/tv_Id"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:layout_marginTop="16dp"
                android:layout_marginEnd="16dp"
                android:textSize="32sp"
                android:textColor="@color/black"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:text="ID" />
    
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/rv_inner_list"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:layout_marginEnd="16dp"
                app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/tv_Id" />
    
        </androidx.constraintlayout.widget.ConstraintLayout>
    
    </androidx.cardview.widget.CardView>
    

    item_inner_list.xml layout for inner recylerVoews' viewholder

        <?xml version="1.0" encoding="utf-8"?>
        <androidx.constraintlayout.widget.ConstraintLayout 
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <TextView
            android:id="@+id/tv_value"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="16dp"
            android:textColor="@color/black"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="value" />
    
        <TextView
            android:id="@+id/tv_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="8dp"
            android:textColor="@color/black"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_value"
            tools:text="content" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>