I have 2 RecyclerView
Adapter named AddDrinkAdapter
and DrinkDetailAdapter
.
AddDrinkAdapter
is to list different types of drinks and the desired quantity.
While DrinkDetailAdapter
is to list those drinks with its detail.
This is my AddDrinkAdapter
Class
public class AddDrinkAdapter extends RecyclerView.Adapter<AddDrinkViewHolder> {
private ArrayList<DrinkQuantity> listDrinkQuantity = new ArrayList<>();
private AddDrinkFragment.callbackListener listener;
private final int min = 0;
private final int max = 5;
AddDrinkAdapter(ArrayList<DrinkQuantity> listDrinkQuantity, AddDrinkFragment.callbackListener callbackListener) {
this.listDrinkQuantity = listDrinkQuantity;
listener = callbackListener;
}
public ArrayList<DrinkQuantity> getListDrinkQuantity() {
return listDrinkQuantity;
}
@NonNull
@Override
public AddDrinkViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_drink_quantity, parent, false);
return new AddDrinkViewHolder(itemView);
}
@SuppressLint("SetTextI18n")
@Override
public void onBindViewHolder(@NonNull AddDrinkViewHolder holder, int position) {
DrinkQuantity drinkQuantity = listDrinkQuantity.get(position);
holder.mTvName.setText(drinkQuantity.getDrinkType());
holder.mEtQuantity.setText(Integer.toString(drinkQuantity.getQuantity()));
holder.mEtQuantity.addTextChangedListener(setTextWatcher(position, holder.mEtQuantity));
holder.mIvMinus.setOnClickListener(onClick(holder, position, "minus"));
holder.mIvAdd.setOnClickListener(onClick(holder, position, "add"));
}
@Override
public int getItemCount() {
return listDrinkQuantity.size();
}
private TextWatcher setTextWatcher(int position, EditText et){
return new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
String value = charSequence.toString();
if(!Util.isNullOrEmpty(value.trim())) {
int num = Integer.parseInt(charSequence.toString());
if(num < 0 || num > 5) {
listener.onChangeText(min, max, num);
et.setText("0");
} else {
listDrinkQuantity.get(position).quantity = num;
}
}
}
@Override
public void afterTextChanged(Editable editable) {
}
};
}
private View.OnClickListener onClick(@NonNull AddDrinkViewHolder holder, int position, String mode) {
return new View.OnClickListener() {
@SuppressLint("SetTextI18n")
@Override
public void onClick(View view) {
int qty = listDrinkQuantity.get(position).quantity;
if (mode.equals("minus")) {
if(qty == 0) {
listener.onChangeText(min,max,-1);
return; // min 0 : abort
}
qty--;
holder.mEtQuantity.setText(Integer.toString(qty));
} else if (mode.equals("add")) {
if(qty == 5) {
listener.onChangeText(min,max,6);
return; // max 5 : abort
}
qty++;
holder.mEtQuantity.setText(Integer.toString(qty));
}
}
};
}
}
DrinkDetailAdapter
Class
public class DrinkDetailAdapter extends RecyclerView.Adapter<DrinkDataViewHolder> {
private static final String TAG = DrinkDetailAdapter.class.getSimpleName();
private ArrayList<Drink> drinkList = new ArrayList<>();
private final String NAME = "name";
private final String SUGAR = "sugar";
private final String ICE = "ice";
DrinkDetailAdapter(ArrayList<Drink> drink_list) {
drinkList = drink_list;
}
ArrayList<Drink> getDrinkDetailsArrayList() {
return drinkList;
}
void reset() {
for(int i=0; i < drinkList.size(); i++) {
drinkList.get(i).getDrinkDetails().drink_name = "";
drinkList.get(i).getDrinkDetails().sugar = "0";
drinkList.get(i).getDrinkDetails().ice = "0";
}
}
@NonNull
@Override
public DrinkDataViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_drink_check, parent, false);
return new DrinkDataViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull DrinkDataViewHolder holder, int position) {
Drink drink = drinkList.get(position);
holder.mEtName.setText(drink.getDrinkDetails().drink_name);
holder.mEtSugar.setText(drink.getDrinkDetails().sugar);
holder.mEtIce.setText(drink.getDrinkDetails().ice);
holder.mEtName.addTextChangedListener(updateData(position, NAME, holder));
holder.mEtSugar.addTextChangedListener(updateData(position, SUGAR, holder));
holder.mEtIce.addTextChangedListener(updateData(position, ICE, holder));
}
@Override
public int getItemCount() {
return drinkList.size();
}
TextWatcher updateData(int position, String editText, @NonNull DrinkDataViewHolder holder) {
return new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
switch (editText) {
case NAME:
drinkList.get(position).getDrinkDetails().drink_name = charSequence.toString();
break;
case SUGAR:
drinkList.get(position).getDrinkDetails().sugar = charSequence.toString();
break;
case ICE:
drinkList.get(position).getDrinkDetails().ice = charSequence.toString();
break;
}
}
@Override
public void afterTextChanged(Editable editable) {
}
};
}
}
After getting Drink
quantities, this is the code to update the DrinkDetailAdapter
ArrayList
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_ADD_DRINK) {
if (resultCode == Activity.RESULT_OK) {
try {
Bundle args = data.getBundleExtra("args");
if (args != null) {
drinkCheckResponse = args.getParcelable(DRINK_CHECK_RESPONSE);
drinkQuantityList.clear();
drinkQuantityList.addAll(drinkCheckResponse.getData().drink_quantities);
}
} catch (Exception e) {
Logger.e(TAG, "onActivityResult Exception: msg[%s]", e.getMessage());
Logger.logException(e);
}
updateDrinkList();
}
}
}
private void updateDrinkList() {
for(int i = 0; i < drinkQuantityList.size(); i++) {
setDrinkList(drinkQuantityList.get(i).drink_type, drinkQuantityList.get(i).quantity);
}
drinkDetailsAdapter.notifyDataSetChanged();
}
private void setDrinkList(String name, int enteredQuantity) {
int drinkTypeCount = drinkTypeCount(name);
int diffCount = enteredQuantity-drinkTypeCount;
if (diffCount != 0) {
addRemoveDrink(diffCount, enteredQuantity, name);
}
}
private int drinkTypeCount(String name) {
int count = 0;
for(int i=0; i < drinkList.size(); i++) {
String subName = drinkList.get(i).getDrinkDetails().drink_name.substring(0, drinkList.get(i).getDrinkDetails().drink_name.indexOf("(")).trim();
if(subName.equals(name)) {
count++;
}
}
return count;
}
private void addRemoveDrink(int diffCount, int enteredQuantity, String name) {
if(diffCount > 0) {
for(int i=enteredQuantity-diffCount+1; i <= enteredQuantity; i++) {
DrinkDetails drinkDetails = new DrinkDetails(name + " (" + i + ")");
drinkList.add(new Drink(drinkDetails));
}
} else if(diffCount < 0) {
for(int i=enteredQuantity-diffCount; i > enteredQuantity; i--) {
for (Iterator<Drink> iterator = drinkList.iterator(); iterator.hasNext();) {
Drink drink = iterator.next();
if (drink.getDrinkDetails().drink_name.equals(name + " (" + i + ")")) {
// Remove the current element from the iterator and the list.
iterator.remove();
drinkDetailsAdapter.notifyItemRemoved(drinkList.indexOf(drink));
}
}
}
}
Collections.sort(drinkList);
Log.d(TAG, "addRemoveDrink: " + drinkList.size());
}
This is the error that I received when I add 10-15 quantity(total for all drink type), then minus the quantity to 0 or 1 for each type.
E/AndroidRuntime: FATAL EXCEPTION: main
Process: my.com.time.tapins, PID: 4143
java.lang.IndexOutOfBoundsException: Index: 13, Size: 3
at java.util.ArrayList.get(ArrayList.java:437)
at my.com.time.tapins.ui.drinkcheck.DrinkDetailAdapter$3.onTextChanged(DrinkDetailAdapter.java:121)
at android.widget.TextView.sendOnTextChanged(TextView.java:10535)
at android.widget.TextView.setText(TextView.java:6272)
at android.widget.TextView.setText(TextView.java:6097)
at android.widget.TextView.setText(TextView.java:6049)
at my.com.time.tapins.ui.drinkcheck.DrinkDetailAdapter.onBindViewHolder(DrinkDetailAdapter.java:67)
at my.com.time.tapins.ui.drinkcheck.DrinkDetailAdapter.onBindViewHolder(DrinkDetailAdapter.java:21)
at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:6781)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:6823)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:5752)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6019)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5858)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5854)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2230)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1557)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:612)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3924)
at androidx.recyclerview.widget.RecyclerView.onMeasure(RecyclerView.java:3336)
at android.view.View.measure(View.java:24530)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
at android.view.View.measure(View.java:24530)
at androidx.core.widget.NestedScrollView.measureChildWithMargins(NestedScrollView.java:1502)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at androidx.core.widget.NestedScrollView.onMeasure(NestedScrollView.java:556)
at android.view.View.measure(View.java:24530)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at android.view.View.measure(View.java:24530)
at androidx.constraintlayout.widget.ConstraintLayout.internalMeasureChildren(ConstraintLayout.java:1227)
at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(ConstraintLayout.java:1572)
at android.view.View.measure(View.java:24530)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:143)
at android.view.View.measure(View.java:24530)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
at android.view.View.measure(View.java:24530)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at android.view.View.measure(View.java:24530)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
E/AndroidRuntime: at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
at android.view.View.measure(View.java:24530)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at com.android.internal.policy.DecorView.onMeasure(DecorView.java:742)
at android.view.View.measure(View.java:24530)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3006)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1833)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2122)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1721)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7598)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:966)
at android.view.Choreographer.doCallbacks(Choreographer.java:790)
at android.view.Choreographer.doFrame(Choreographer.java:725)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:951)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
E/UncaughtException: java.lang.IndexOutOfBoundsException: Index: 13, Size: 3
at java.util.ArrayList.get(ArrayList.java:437)
at my.com.time.tapins.ui.drinkcheck.DrinkDetailAdapter$3.onTextChanged(DrinkDetailAdapter.java:121)
at android.widget.TextView.sendOnTextChanged(TextView.java:10535)
at android.widget.TextView.setText(TextView.java:6272)
at android.widget.TextView.setText(TextView.java:6097)
at android.widget.TextView.setText(TextView.java:6049)
at my.com.time.tapins.ui.drinkcheck.DrinkDetailAdapter.onBindViewHolder(DrinkDetailAdapter.java:67)
at my.com.time.tapins.ui.drinkcheck.DrinkDetailAdapter.onBindViewHolder(DrinkDetailAdapter.java:21)
at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:6781)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:6823)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:5752)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6019)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5858)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5854)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2230)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1557)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:612)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3924)
at androidx.recyclerview.widget.RecyclerView.onMeasure(RecyclerView.java:3336)
at android.view.View.measure(View.java:24530)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
at android.view.View.measure(View.java:24530)
at androidx.core.widget.NestedScrollView.measureChildWithMargins(NestedScrollView.java:1502)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at androidx.core.widget.NestedScrollView.onMeasure(NestedScrollView.java:556)
at android.view.View.measure(View.java:24530)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at android.view.View.measure(View.java:24530)
at androidx.constraintlayout.widget.ConstraintLayout.internalMeasureChildren(ConstraintLayout.java:1227)
at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(ConstraintLayout.java:1572)
at android.view.View.measure(View.java:24530)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:143)
at android.view.View.measure(View.java:24530)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
at android.view.View.measure(View.java:24530)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at android.view.View.measure(View.java:24530)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
at android.view.View.measure(View.java:24530)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at com.android.internal.policy.DecorView.onMeasure(DecorView.java:742)
at android.view.View.measure(View.java:24530)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3006)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1833)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2122)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1721)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7598)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:966)
at android.view.Choreographer.doCallbacks(Choreographer.java:790)
at android.view.Choreographer.doFrame(Choreographer.java:725)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:951)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Basically this is the 2 pages for this two RecyclerView
Adapter.
Screenshot of adding/removing drink quantities
Screenshot of showing the drink detail
Drink
Class
public class Drink implements Comparable<Drink>, Parcelable {
private DrinkDetails drink_details;
public Drink() {
}
public Drink(DrinkDetails drink_details) {
this.drink_details = drink_details;
}
protected Drink(Parcel in) {
}
public static final Creator<Drink> CREATOR = new Creator<Drink>() {
@Override
public Drink createFromParcel(Parcel in) {
return new Drink(in);
}
@Override
public Drink[] newArray(int size) {
return new Drink[size];
}
};
public DrinkDetails getDrinkDetails() {
return drink_details;
}
private String getDrinkName() {
return getDrinkDetails().drink_name;
}
@Override
public int compareTo(Drink drink) {
if(drink.drink_details.drink_name.compareToIgnoreCase(getDrinkName()) > 0){
return -1;
} else if(drink.drink_details.drink_name.compareToIgnoreCase(getDrinkName()) < 0){
return 1;
}
return 0;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
}
}
DrinkQuantity
Class
public class DrinkQuantity implements Parcelable {
public String drink_type;
public int quantity;
public DrinkQuantity() {
}
public DrinkQuantity(String drink_type, int quantity) {
this.drink_type = drink_type;
this.quantity = quantity;
}
protected DrinkQuantity(Parcel in) {
drink_type = in.readString();
quantity = in.readInt();
}
public static final Creator<DrinkQuantity> CREATOR = new Creator<DrinkQuantity>() {
@Override
public DrinkQuantity createFromParcel(Parcel in) {
return new DrinkQuantity(in);
}
@Override
public DrinkQuantity[] newArray(int size) {
return new DrinkQuantity[size];
}
};
public String getDrinkType() {
return drink_type;
}
public int getQuantity() {
return quantity;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(drink_type);
parcel.writeInt(quantity);
}
}
I find the structure very confusing as the AddDrinkAdapter
uses ArrayList<DrinkQuantity>
and DrinkDetailAdapter
uses ArrayList<Drink>
then I need to perform loop to match those 2 lists. If anyone could change the structure to make this better, please do tell me. Thanks.
After debugging for hours on this issue, finally I saw what was the problem.
My updateData function was the one causing me problem as it is holding the original int position
.
So, this is my updated code to solve this issue.
DrinkDetailAdapter
Class
TextWatcher updateData(String editText, @NonNull DrinkDataViewHolder holder) {
return new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
Drink drink = getDrink(holder.mTvDrinkName.getText().toString());
if(drink == null) return;
switch (editText) {
switch (editText) {
case NAME:
drinkList.get(drinkList.indexOf(drink)).getDrinkDetails().drink_name = charSequence.toString();
break;
case SUGAR:
drinkList.get(drinkList.indexOf(drink)).getDrinkDetails().sugar = charSequence.toString();
break;
case ICE:
drinkList.get(drinkList.indexOf(drink)).getDrinkDetails().ice = charSequence.toString();
break;
}
}
}
@Override
public void afterTextChanged(Editable editable) {
}
};
}
private Drink getDrink(String name) {
for (Drink drink : drinkList) {
if (drink.getDrinkDetails().drink_name.equalsIgnoreCase(name))
return drink;
}
return null;
}