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:
This is how I want the data to be:
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();
}
}
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.
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>