Buttons To Modify Items In An Android ListView

One of my many projects involved building a food ordering system that will be used across a chain of restaurants and as you know with every project comes its unique challenges. I will focus this article on one of the challenges which I feel is really cool which is modifying(increasing and decreasing) and deleting ListView items from an adapter.So shoot.

Follow this link to get the demo on github

Demo Application Screenshots

First, lets create the views that will be needed to bring home this idea.Create an initial activity and fill it in with the code snippet below.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.oyinloyeayodeji.www.meals.MainActivity">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/selected_food_list"
android:paddingBottom="100dp">
</ListView>

</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentBottom="true">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Total Due:"
android:textAppearance="?android:textAppearanceMedium"
android:layout_weight="1"
android:padding="@dimen/activity_horizontal_margin"
android:textStyle="bold"
/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:id="@+id/meal_total"
android:layout_weight="1"
android:textAppearance="?android:textAppearanceMedium"
android:textStyle="bold"
android:padding="@dimen/activity_horizontal_margin"
/>
</LinearLayout>
</RelativeLayout>

Next create a layout for a single item on the list which will contain the name of the meal, a button to add quantity, a button to reduce quantity, the quantity textview,the amount and a button to delete a whole food item.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/activity_horizontal_margin">

<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
tools:text="Rice Balls"
android:padding="5dp"
android:layout_gravity="center_vertical"
android:id="@+id/selected_food_name"/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="3dp"
android:layout_gravity="center_vertical"
android:background="@drawable/rectangle_border"
android:drawableLeft="@drawable/ic_add_24dp"
android:id="@+id/plus_meal"/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="3dp"
android:layout_margin="3dp"
android:layout_gravity="center_vertical"
android:background="@drawable/rectangle_border"
android:drawableLeft="@drawable/ic_remove_black"
android:id="@+id/minus_meal"/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="right"
android:text="x 1"
android:layout_gravity="center_vertical"
android:id="@+id/quantity"/>

<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="right"
tools:text="455"
android:layout_gravity="center_vertical"
android:id="@+id/selected_food_amount"/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="3dp"
android:layout_margin="3dp"
android:layout_gravity="center_vertical"
android:background="@drawable/rectangle_border"
android:drawableLeft="@drawable/ic_close"
android:id="@+id/delete_item"/>
</LinearLayout>

With the layout out of the way, now we create a class which will be used to illustrate the idea of buttons in the listview.

public class Food implements Serializable{

private String mName;
private int mAmount;
private int mQuantity;

public void setmQuantity(int mQuantity) {
this.mQuantity = mQuantity;
}

public Food(){}

public Food(String mName, int mAmount) {
this.mName = mName;
this.mAmount = mAmount;
this.mQuantity = 1;
}

public String getmName() {
return mName;
}

public int getmAmount() {
return mAmount;
}

public int getmQuantity(){
return mQuantity;
}

public void addToQuantity(){
this.mQuantity += 1;
}

public void removeFromQuantity(){
if(this.mQuantity > 1){
this.mQuantity -= 1;
}
}

}

The food class has a name, an amount which we use to calculate the total and quantity which will be varied based on the choice of the user. Other complexities have been taken out for clarity.

Then to the holy grail which far more simpler than you might have expected. We create the OrderAdapter as shown below.

public class OrderAdapter extends ArrayAdapter<Food>{
private List<Food> list;
private Context context;

TextView currentFoodName,
currentCost,
quantityText,
addMeal,
subtractMeal,
removeMeal;

public OrderAdapter(Context context, List<Food> myOrders) {
super(context, 0, myOrders);
this.list = myOrders;
this.context = context;
}


public View getView(final int position, View convertView, ViewGroup parent){
View listItemView = convertView;
if(listItemView == null){
listItemView = LayoutInflater.from(getContext()).inflate(
R.layout.item_my_meal,parent,false
);
}

final Food currentFood = getItem(position);

currentFoodName = (TextView)listItemView.findViewById(R.id.selected_food_name);
currentCost = (TextView)listItemView.findViewById(R.id.selected_food_amount);
subtractMeal = (TextView)listItemView.findViewById(R.id.minus_meal);
quantityText = (TextView)listItemView.findViewById(R.id.quantity);
addMeal = (TextView)listItemView.findViewById(R.id.plus_meal);
removeMeal = (TextView)listItemView.findViewById(R.id.delete_item);

//Set the text of the meal, amount and quantity
currentFoodName.setText(currentFood.getmName());
currentCost.setText("GH"+"\u20B5"+" "+ (currentFood.getmAmount() * currentFood.getmQuantity()));
quantityText.setText("x "+ currentFood.getmQuantity());

//OnClick listeners for all the buttons on the ListView Item
addMeal.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
currentFood.addToQuantity();
quantityText.setText("x "+ currentFood.getmQuantity());
currentCost.setText("GH"+"\u20B5"+" "+ (currentFood.getmAmount() * currentFood.getmQuantity()));
notifyDataSetChanged();
}
});

subtractMeal.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
currentFood.removeFromQuantity();
quantityText.setText("x "+currentFood.getmQuantity());
currentCost.setText("GH"+"\u20B5"+" "+ (currentFood.getmAmount() * currentFood.getmQuantity()));
notifyDataSetChanged();
}
});

removeMeal.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
list.remove(position);
notifyDataSetChanged();
}
});

return listItemView;
}

}

Notice that i call notifyDataSetChanged() on the elements of the view that will be modifying the view. These ideally should be encapsulated in a separate method to make our code DRY.

Then finally the activity is created with an ArrayList of food items and a calculateMealTotal method which loops through the ArrayList and sums up the prices. But of utmost importance is the DataSetObserver which is created to always listen for changes on a particular adapter element and make the necessary changes on the view in case there are any other elements outside the ListView that accesses the values of the ListView such as the total price which is being calculated based on the quantity and amount.

public class MainActivity extends AppCompatActivity {

TextView mealTotalText;
ArrayList<Food> orders;

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

ListView storedOrders = (ListView)findViewById(R.id.selected_food_list);

orders = getListItemData();
mealTotalText = (TextView)findViewById(R.id.meal_total);
OrderAdapter adapter = new OrderAdapter(this, orders);

storedOrders.setAdapter(adapter);
adapter.registerDataSetObserver(observer);
}

public int calculateMealTotal(){
int mealTotal = 0;
for(Food order : orders){
mealTotal += order.getmAmount() * order.getmQuantity();
}
return mealTotal;
}

DataSetObserver observer = new DataSetObserver() {
@Override
public void onChanged() {
super.onChanged();
setMealTotal();
}
};

private ArrayList<Food> getListItemData(){
ArrayList<Food> listViewItems = new ArrayList<Food>();
listViewItems.add(new Food("Rice",30));
listViewItems.add(new Food("Beans",40));
listViewItems.add(new Food("Yam",60));
listViewItems.add(new Food("Pizza",80));
listViewItems.add(new Food("Fries",100));

return listViewItems;
}

public void setMealTotal(){
mealTotalText.setText("GH"+"\u20B5"+" "+ calculateMealTotal());
}
}

That sums it all up. Have fun coding. Share and leave a comment if it helped you or you discovered any error or more efficient way to get the job done.

Cheers.