Build a shopping cart with Kotlin - Part 2: Building the shopping cart logic

Introduction

Introduction

In this second tutorial of this series, we’ll build the shopping cart logic and other things involved in the process. This should allow our users to add products to the cart and know exactly the size of the cart. It’s time to dive into the code.

In the previous tutorial, we’ve built an app that is able to fetch some data from a remote web service and render them using a recycler view to the users.

Prerequisites

To follow along with this tutorial, you will need the following:

Build the shopping cart logic

We’ll be building the shopping cart logic and workflow. First, let’s create a CartItem entity (a CartItem.kt as it happens) which represents a single shopping cart item in the ../app/src/main/java/yourPackage directory:

    data class CartItem(var product: Product, var quantity: Int = 0)

As you can see, this class is pretty straightforward, it only has two fields:

  • the product to manipulate: product, an instance of the Product class we defined in the first part of the tutorial
  • and the product quantity: quantity, just an integer, by default equal to zero

Next, we’ll define a custom class responsible for handling the shopping cart operations such as adding items to the cart, removing items from the cart, getting the cart size and so on.

First, you must add this dependency Paper into your app. It’s a simple but efficient NoSQL-like for Android apps. This library will made things easily for us as we no more need to maintain an SQLite database which is very cumbersome. If you need some info about how to use the Paper library, you can read the documentation here.

Now, head over to your build.gradle file, then add the following to your dependencies block:

1//..app/build.gradle
2    implementation 'io.paperdb:paperdb:2.6'

Our custom class ShoppingCart is a wrapper around the Paper library, and contains a set of useful (static) functions for specific purposes. We made use of the Paper library to store our shoppping cart data, and retrieve them whenever we want.

Create a ShoppingCart.kt file and add the following class:

1//..app/src/main/java/yourPackage/ShoppingCart.kt
2    import android.content.Context
3    import android.widget.Toast
4    import io.paperdb.Paper
5    
6    class ShoppingCart {
7    
8        companion object {
9            fun addItem(cartItem: CartItem) {
10              val cart = ShoppingCart.getCart()
11    
12              val targetItem = cart.singleOrNull { it.product.id == cartItem.product.id }
13                if (targetItem == null) {
14                    cartItem.quantity++
15                    cart.add(cartItem)
16                } else {
17                    targetItem.quantity++
18                }
19                ShoppingCart.saveCart(cart)
20            }
21    
22            fun removeItem(cartItem: CartItem, context: Context) {
23              val cart = ShoppingCart.getCart()
24    
25              val targetItem = cart.singleOrNull { it.product.id == cartItem.product.id }
26                if (targetItem != null) {
27                    if (targetItem.quantity > 0) {
28                        targetItem.quantity--
29                    } else {
30                        cart.remove(targetItem)
31                    }
32                }
33    
34                ShoppingCart.saveCart(cart)
35            }
36    
37            fun saveCart(cart: MutableList<CartItem>) {
38                Paper.book().write("cart", cart)
39            }
40    
41            fun getCart(): MutableList<CartItem> {
42                return Paper.book().read("cart", mutableListOf())
43            }
44    
45            fun getShoppingCartSize(): Int {
46                var cartSize = 0
47                ShoppingCart.getCart().forEach {
48                    cartSize += it.quantity;
49                }
50    
51                return cartSize
52            }
53        }
54    
55    }

We have five dedicated functions in our class:

  • addItem: this function basically gets a cartItem instance as a parameter, checks first whether it is on the cart or not. If it’s in the cart, we just increment its quantity by one, if this is not the case not only we increment its quantity but we also add it the cart and save the shopping cart to keep it updated.

  • removeItem: here, we also check if the cartItem instance is present in the cart, then we remove it from the shopping cart if its quantity is equal to zero, but if its quantity is superior to zero we just decreased it by one. Finally, we keep the cart updated by saving it.

  • saveCart: with the help of this function, we can save the state of our app

  • getCart: this function returns our shopping cart itself, basically a list of CartItem

  • getShoppingCartSize: helps us get the actual number of items present in our cart

Add remove/add buttons to the design with their listeners

We just added two buttons to our product cart design, one for adding the product to the shopping cart and the second one to remove it. So amend your product_row_item layout file like the following:

1//../app/src/main/res/layout/product_row_item.xml
2    
3    <?xml version="1.0" encoding="utf-8"?>
4    <android.support.v7.widget.CardView
5            xmlns:card_view="http://schemas.android.com/tools"
6            xmlns:android="http://schemas.android.com/apk/res/android"
7            xmlns:app="http://schemas.android.com/apk/res-auto"
8            app:cardUseCompatPadding="true"
9            android:layout_margin="2dp"
10            app:cardBackgroundColor="@android:color/white"
11            app:cardCornerRadius="4dp"
12            android:background="?attr/selectableItemBackground"
13            app:cardElevation="3dp"
14            android:foreground="?attr/selectableItemBackground"
15            card_view:cardElevation="4dp"
16            android:layout_width="match_parent"
17            android:layout_height="wrap_content">
18    
19        <LinearLayout
20                android:orientation="vertical"
21                android:layout_width="match_parent"
22                android:layout_height="match_parent">
23    
24            <ImageView
25                    android:id="@+id/product_image"
26                    android:layout_width="match_parent"
27                    android:layout_height="180dp"/>
28    
29            <LinearLayout
30                    android:layout_marginStart="5dp"
31                    android:layout_marginLeft="5dp"
32                    android:padding="10dp"
33                    android:orientation="vertical"
34                    android:layout_width="match_parent"
35                    android:layout_height="wrap_content">
36    
37    
38                <TextView
39                        android:textColor="@android:color/black"
40                        android:textSize="22sp"
41                        android:layout_marginBottom="12dp"
42                        android:id="@+id/product_name"
43                        android:layout_width="wrap_content"
44                        android:layout_height="wrap_content"/>
45    
46    
47                <TextView
48                        android:textSize="19sp"
49                        android:id="@+id/product_price"
50                        android:textColor="@android:color/holo_red_light"
51                        android:layout_width="wrap_content"
52                        android:layout_height="wrap_content"/>
53            </LinearLayout>
54    
55    
56            <LinearLayout
57                    android:layout_gravity="end"
58                    android:orientation="horizontal"
59                    android:layout_width="wrap_content"
60                    android:layout_height="wrap_content">
61    
62                <ImageButton
63                        android:id="@+id/removeItem"
64                        android:layout_width="wrap_content"
65                        android:paddingHorizontal="16dp"
66                        android:tint="@android:color/white"
67                        android:paddingVertical="4dp"
68                        android:src="@drawable/ic_remove_shopping_cart"
69                        android:background="@color/colorPrimary"
70                        android:layout_height="wrap_content"
71                        card_view:targetApi="o"/>
72    
73                <ImageButton
74                        android:id="@+id/addToCart"
75                        android:paddingHorizontal="16dp"
76                        android:tint="@android:color/white"
77                        android:paddingVertical="4dp"
78                        android:src="@drawable/ic_add_shopping"
79                        android:background="@color/colorAccent"
80                        android:layout_width="wrap_content"
81                        android:layout_height="wrap_content"
82                        card_view:targetApi="o"/>
83    
84            </LinearLayout>
85    
86    
87        </LinearLayout>
88    
89    </android.support.v7.widget.CardView>

Here you can find the ic_remove_shopping_cart resource file to paste in ../app/src/main/res/drawable folder.

Now, head over to your ProductAdapter file, and add the following inside the view holder inner class in the bindProduct method body:

1../app/src/main/java/yourPackage/ProductAdapter.kt
2    
3    import android.content.Context
4    import android.support.design.widget.Snackbar
5    import android.support.v7.widget.RecyclerView
6    import android.view.LayoutInflater
7    import android.view.View
8    import android.view.ViewGroup
9    import android.widget.Toast
10    import com.squareup.picasso.Picasso
11    import io.paperdb.Paper
12    import kotlinx.android.synthetic.main.activity_main.*
13    import kotlinx.android.synthetic.main.product_row_item.view.*
14    
15    class ViewHolder(){
16    
17    ......
18    ......
19    ......
20    
21      
22      fun bindProduct(product:Product){
23      ...
24      ...
25      ...
26      
27       itemView.addToCart.setOnClickListener { view ->
28        
29            val item = CartItem(product)
30    
31            ShoppingCart.addItem(item)
32            //notify users
33            Snackbar.make(
34                (itemView.context as MainActivity).coordinator,
35                "${product.name} added to your cart",
36                Snackbar.LENGTH_LONG
37            ).show()
38    
39        }
40    
41        itemView.removeItem.setOnClickListener { view ->
42    
43            val item = CartItem(product)
44    
45            ShoppingCart.removeItem(item, itemView.context)
46    
47            Snackbar.make(
48                (itemView.context as MainActivity).coordinator,
49                "${product.name} removed from your cart",
50                Snackbar.LENGTH_LONG
51            ).show()
52    
53        }
54      
55      }
56        
57    }

We just add the listeners to our buttons. When the addToCart button is clicked, we create a new instance of the CartItem class is created provided with the product to add, then we add it to the shopping cart with the help of the addItem utility function we created. Next, we show a snack bar to the user to give them a visual feedback about the success of his action. The process is the same for the removeToCart click event, except that we removed the item from the shopping cart using our removeItem utility function.

Add the shopping cart size counter

Now, we want to let our users know our cart size, and keep them updated when an item is added or removed, as you can see in the picture below. Let’s add this button, and the counter to our src/main/res/layout/activity_main.xml as well as the logic required to make it work as expected. Basically the counter is a text view updated with the shopping cart size.

First, we designed a custom shape to provide a background for our shopping cart counter

1../app/src/main/java/res/drawable/item_counter.xml
2    
3    <?xml version="1.0" encoding="utf-8"?>
4    <selector xmlns:android="http://schemas.android.com/apk/res/android">
5        <item>
6            <shape android:shape="oval">
7                <solid android:color="@color/colorPrimary"/>
8                <corners android:radius="50dp"/>
9                <size android:height="25dp"/>
10                <size android:width="25dp"/>
11            </shape>
12        </item>
13    </selector>

Add the following after the SwipeRefreshLayout in the file.

1../src/main/java/res/layout/activity_main.xml
2     ...
3    </android.support.v4.widget.SwipeRefreshLayout>
4    
5    <RelativeLayout
6            android:id="@+id/showCart"
7            android:layout_margin="16dp"
8            android:layout_gravity="bottom|end"
9            android:layout_width="wrap_content"
10            android:layout_height="wrap_content">
11    
12        <android.support.design.widget.FloatingActionButton
13                android:id="@+id/basketButton"
14                android:src="@drawable/ic_shopping_basket"
15                android:tint="@android:color/white"
16                app:fabSize="normal"
17                android:layout_width="wrap_content"
18                android:layout_height="wrap_content"/>
19    
20        <TextView
21                android:padding="8dp"
22                android:layout_marginBottom="25dp"
23                android:elevation="50dp"
24                android:layout_marginStart="5dp"
25                android:textColor="@android:color/white"
26                android:textStyle="bold"
27                android:layout_alignRight="@id/basketButton"
28                android:id="@+id/cart_size"
29                android:textSize="13sp"
30                android:background="@drawable/item_counter"
31                android:layout_width="wrap_content"
32                android:layout_height="wrap_content"
33                android:layout_alignEnd="@id/basketButton"
34                tools:targetApi="lollipop"
35                android:layout_marginLeft="15dp"/>
36    
37    </RelativeLayout>

Now, we can refer to our counter with the help of its ID, and provide it with the shopping cart size. Just add this line to your file in the onCreate method, before the getProducts method.

1../app/src/main/java/yourPackage/MainActivity.kt
2    
3    cart_size.text = ShoppingCart.getShoppingCartSize().toString()

Test your app

Now if you run your app and test it, first you should have get the shopping cart shown in the counter. And if you try to add or remove items I guess you get the visual feedback, I mean the snackbar we provided the user with to keep him updated with what it’s going on, but strangely you’re not seeing the counter updated with the actual cart size. You may think that something is going wrong but this is quite normal. Our app is not reactive yet, and we have not even provided it with a means to behave in such an expected way. Well, in the following part we’ll see a more advanced topic, RxKotlin Reactive programming in Kotlin which hopefully will help us add some reactivity to our shopping cart app.

Conclusion

Until now, you’ve learned how to show a list of products to users, built your shopping cart logic enabling your users to add and remove items from your cart.

In the last, part third of this tutorial series we’ll make our mobile app more reactive using a powerful tool: RxKotlin. We’ll also add a shopping cart review functionality to our app to make it more complete 😉.

Nevertheless, you can grab the code for the second part here.