🎉 New! Web Push Notifications for Chatkit. Learn more in our latest blog post.
Hide
Products
chatkit_full-logo

Extensible API for in-app chat

channels_full-logo

Build scalable realtime features

beams_full-logo

Programmatic push notifications

Developers

Docs

Read the docs to learn how to use our products

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Sign in
Sign up

Build a shopping cart with Kotlin - Part 3: Adding reactivity to the app

  • Ethiel Adiassa

March 12th, 2019
You will need an Android development environment set up on your machine. Some knowledge of Android development is required.

Introduction

In this last part of the tutorial series, we will add some reactivity to our app using RxKotlin, and complete our shopping cart.

Prerequisites

In order to follow along with the tutorial, make sure you meet the following requirements:

  • IntelliJ IDEA or Android Studio.
  • Experience with the Kotlin language
  • Having followed and completed the previous parts of the tutorial series: Part 1 Part 2
  • An emulator or physical device (for app testing/debugging purpose)

What’s RxKotlin?

You may not know what RxKotlin is about. Well I cannot blame you for that 🙂 . The first thing you should know is that RxKotlin is about Reactive programming with Kotlin. And you may also ask what is reactive programming, right ?

According to Wikipedia,

Reactive programming is a programming paradigm oriented around data flows and the propagation of change. This means that it should be possible to express static or dynamic data flows with ease in the programming languages used, and that the underlying execution model will automatically propagate changes through the data flow

This paradigm is built on the ideology of the observable pattern, and provides us with many tools to build reactive apps. We have essentially three components involved in reactive programming:

  • Observers: an observer subscribes to an observable.
  • Observables: an observable emits a stream of data which the the observer listens for and reacts to.
  • Operators: they allow the observer to perform a set of really useful operations over the sequence of items emitted by the the observable.

Now that you grasp some basic concepts of reactive programming, I can explain you what RxKotlin is about.

According to the official definition,

RxKotlin is a lightweight library that adds convenient extension functions to RxJava. You can use RxJava with Kotlin out-of-the-box, but Kotlin has language features (such as extension functions) that can streamline usage of RxJava even more. RxKotlin aims to conservatively collect these conveniences in one centralized library, and standardize conventions for using RxJava with Kotlin.

So from what is said, we see that RxKotlin is a wrapper around RxJava which is itself an open-source implementation of the ReactiveX library that helps you create applications in the reactive programming style.

Now we can move on to the implementation of this concept into our shopping cart app.

Adding reactivity to the counter with RxKotlin

Now, head over to your build.gradle file, then add the RxJava and RxKotlin libraries to your dependencies block:

    //..app/build.gradle
    implementation 'io.reactivex.rxjava2:rxjava:2.1.9'
    implementation 'io.reactivex.rxjava2:rxkotlin:2.2.0'

You remembered in the previous part we’ve added some listeners to our buttons to perform some operations over our cart. We’ll update this section, we’ll add a taste of reactivity using the really cool possibilities RxKotlin offers us.

Move to your ProductAdapter file where you defined listeners,


     itemView.addToCart.setOnClickListener { view ->

            val item = CartItem(product)

            ShoppingCart.addItem(item)
            //notify users
            Snackbar.make(
                (itemView.context as MainActivity).coordinator,
                "${product.name} added to your cart",
                Snackbar.LENGTH_LONG
            ).show()

        }

        itemView.removeItem.setOnClickListener { view ->

            val item = CartItem(product)

            ShoppingCart.removeItem(item, itemView.context)

            Snackbar.make(
                (itemView.context as MainActivity).coordinator,
                "${product.name} removed from your cart",
                Snackbar.LENGTH_LONG
            ).show()

        }

and amend this section like the following:

    ../app/src/main/java/yourPackage/ProductAdapter.kt

    Observable.create(ObservableOnSubscribe<MutableList<CartItem>> {

        itemView.addToCart.setOnClickListener { view ->

            val item = CartItem(product)

            ShoppingCart.addItem(item)
            //notify users
            Snackbar.make(
                (itemView.context as MainActivity).coordinator,
                "${product.name} added to your cart",
                Snackbar.LENGTH_LONG
            ).show()

            it.onNext(ShoppingCart.getCart())

        }

        itemView.removeItem.setOnClickListener { view ->

            val item = CartItem(product)

            ShoppingCart.removeItem(item, itemView.context)

            Snackbar.make(
                (itemView.context as MainActivity).coordinator,
                "${product.name} removed from your cart",
                Snackbar.LENGTH_LONG
            ).show()

            it.onNext(ShoppingCart.getCart())
        }


    }).subscribe { cart ->
        var quantity = 0

        cart.forEach { cartItem ->
            quantity += cartItem.quantity
        }

        (itemView.context as MainActivity).cart_size.text = quantity.toString()
        Toast.makeText(itemView.context, "Cart size $quantity", Toast.LENGTH_SHORT).show()
    }

What we’ve done above is pretty simple. We’ve created an Observable from our shopping cart data as we know it is basically a list of CartItem:

    Observable.create(ObservableOnSubscribe<MutableList<CartItem>> {

Then whenever an item is added or removed, we pulled the new state of the shopping cart to the data stream with this line: it.onNext(ShoppingCart.getCart()).

Next we subscribe to this stream of data, once we get it, we made some basic operation to get the updated shopping cart size and finally we display it on the counter: (itemView.context as MainActivity).cart_size.text = quantity.toString(). Pretty simple, right?

You should agree that in just a few lines of code, we’ve added some reactivity to our shopping cart 😎.

Now if you run and test your app, you should see it behaving like expected. No more disappointment like in the previous article 😁.

Let’s finish our app by adding a review functionality with a Checkout button.

Adding the review functionality to the shopping cart

In this section, we’ll build an activity responsible for reviewing our shopping cart items as well as convenient details.

Create a cart_list_item file and paste the following inside. This layout is responsible for handling the view of a single cart item. It simply contains the cart item image, its name, price and quantity.

    <?xml version="1.0" encoding="utf-8"?>
    //../app/src/main/java/res/layout/cart_list_item.xml
    <android.support.v7.widget.CardView
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            app:cardUseCompatPadding="true"
            app:cardElevation="1.5dp"
            android:layout_margin="2dp"
            app:cardBackgroundColor="@android:color/white"
            app:cardCornerRadius="2dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

        <LinearLayout
                xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:gravity="center_vertical"
                android:weightSum="1.5"
                android:layout_height="match_parent">


            <ImageView
                    android:layout_weight=".5"
                    android:scaleType="fitXY"
                    android:id="@+id/product_image"
                    android:layout_width="170dp"
                    android:layout_height="135dp"/>


            <LinearLayout
                    android:layout_weight=".5"
                    android:gravity="center_vertical"
                    android:layout_marginRight="8dp"
                    android:padding="8dp"
                    android:orientation="vertical"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_marginEnd="8dp">


                <TextView
                        android:textSize="19sp"
                        android:textColor="@android:color/black"
                        android:id="@+id/product_name"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"/>


                <TextView
                        android:layout_marginTop="10dp"
                        android:textSize="16sp"
                        android:textStyle="bold"
                        android:id="@+id/product_price"
                        android:textColor="@android:color/holo_red_light"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"/>


            </LinearLayout>


            <TextView
                    android:textStyle="bold"
                    android:layout_marginEnd="12dp"
                    android:gravity="center_vertical"
                    android:layout_gravity="center_vertical"
                    android:padding="8dp"
                    android:textColor="@android:color/black"
                    android:textSize="16sp"
                    android:background="@drawable/round_background"
                    android:id="@+id/product_quantity"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginRight="12dp"/>

        </LinearLayout>
    </android.support.v7.widget.CardView>

We’ll also a need adapter to render our shopping cart items properly. Create a ShoppingCartAdapter.kt file and paste the following inside:

    //..app/src/main/java/yourPackage/ShoppingCartAdapter.kt

    import android.content.Context
    import android.support.v7.widget.RecyclerView
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import com.squareup.picasso.Picasso
    import kotlinx.android.synthetic.main.cart_list_item.view.*

    class ShoppingCartAdapter(var context: Context, var cartItems: List<CartItem>) :
        RecyclerView.Adapter<ShoppingCartAdapter.ViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, p1: Int): ShoppingCartAdapter.ViewHolder {

            // The layout design used for each list item
            val layout = LayoutInflater.from(context).inflate(R.layout.cart_list_item, parent, false)

            return ViewHolder(layout)
        }

            // This returns the size of the list.
        override fun getItemCount(): Int = cartItems.size

        override fun onBindViewHolder(viewHolder: ShoppingCartAdapter.ViewHolder, position: Int) {

         //we simply call the `bindItem` function here
            viewHolder.bindItem(cartItems[position])
        }
        class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {

            fun bindItem(cartItem: CartItem) {

            // This displays the cart item information for each item
                Picasso.get().load(cartItem.product.photos[0].filename).fit().into(itemView.product_image)

                itemView.product_name.text = cartItem.product.name

                itemView.product_price.text = "$${cartItem.product.price}"

                itemView.product_quantity.text = cartItem.quantity.toString()

            }
        }

    }

Now, we are a few to finish our mobile app. The next step is build the activity responsible for handing the review of our shopping cart. It will have the following rendering 😋

Create an empty activity named ShoppingCartActivity or whatever you want. Then, move on to your activity_shopping_cart.xml file and replace the content with the following:

    //../app/src/main/java/res/layout/activity_shopping_cart.xml

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:layout_width="match_parent"
            android:background="#fffffdff"
            android:layout_height="match_parent"
            tools:context=".ShoppingCartActivity">

        <android.support.design.widget.AppBarLayout
                android:background="@android:color/transparent"
                android:fitsSystemWindows="true"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize">

            <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    app:titleTextColor="@color/colorAccent"
                    app:title="Shopping Cart"
                    android:background="@android:color/white"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"/>

        </android.support.design.widget.AppBarLayout>


        <android.support.v7.widget.RecyclerView
                app:layout_behavior="@string/appbar_scrolling_view_behavior"
                android:id="@+id/shopping_cart_recyclerView"
                android:layout_margin="2dp"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>


        <RelativeLayout
                android:padding="8dp"
                app:elevation="4dp"
                android:layout_gravity="bottom"
                android:elevation="15dp"
                android:background="@color/colorAccent"
                android:layout_width="match_parent"
                android:layout_height="66dp"
                tools:targetApi="lollipop">


            <LinearLayout
                    android:layout_alignParentStart="true"
                    android:layout_centerVertical="true"
                    android:layout_width="wrap_content"
                    android:layout_marginLeft="15dp"
                    android:layout_height="wrap_content"
                    tools:ignore="RtlCompat">

                <TextView
                        android:id="@+id/totalLabel"
                        android:textSize="18sp"
                        android:textStyle="bold"
                        android:text="Total"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        />

                <TextView
                        android:layout_marginStart="18dp"
                        android:id="@+id/total_price"
                        android:textSize="24sp"
                        android:textColor="@android:color/white"
                        android:textStyle="bold"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        tools:ignore="RtlCompat"/>

            </LinearLayout>


            <Button
                    android:textSize="19sp"
                    android:layout_centerVertical="true"
                    android:layout_marginEnd="15dp"
                    android:layout_marginRight="15dp"
                    android:padding="10dp"
                    android:layout_alignParentEnd="true"
                    android:text="Checkout"
                    android:textAllCaps="false"
                    android:background="@drawable/round_background"
                    android:textColor="@android:color/white"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentRight="true"/>


        </RelativeLayout>

    </android.support.design.widget.CoordinatorLayout>

The above layout contains a recycler view to display our shopping cart items, and a bottom bar containing the total price of our shopping and a Checkout button.

Also, replace the content of your ShoppingCartActivity like the following:

    //..app/src/main/java/yourPackage/ShoppingCartActivity.kt

    import android.graphics.PorterDuff
    import android.support.v7.app.AppCompatActivity
    import android.os.Bundle
    import android.support.v4.content.ContextCompat
    import android.support.v7.widget.LinearLayoutCompat
    import android.support.v7.widget.LinearLayoutManager
    import android.support.v7.widget.RecyclerView
    import android.view.MenuItem
    import kotlinx.android.synthetic.main.activity_shopping_cart.*

    class ShoppingCartActivity : AppCompatActivity() {

        lateinit var adapter: ShoppingCartAdapter

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_shopping_cart)

            setSupportActionBar(toolbar)

            supportActionBar?.setDisplayShowHomeEnabled(true)
            supportActionBar?.setDisplayHomeAsUpEnabled(true)

            val upArrow = ContextCompat.getDrawable(this, R.drawable.abc_ic_ab_back_material)
            upArrow?.setColorFilter(ContextCompat.getColor(this, R.color.colorPrimary), PorterDuff.Mode.SRC_ATOP)
            supportActionBar?.setHomeAsUpIndicator(upArrow)

            adapter = ShoppingCartAdapter(this, ShoppingCart.getCart())
            adapter.notifyDataSetChanged()

            shopping_cart_recyclerView.adapter = adapter

            shopping_cart_recyclerView.layoutManager = LinearLayoutManager(this)

            var totalPrice = ShoppingCart.getCart()
                .fold(0.toDouble()) { acc, cartItem -> acc + cartItem.quantity.times(cartItem.product.price!!.toDouble()) }

            total_price.text = "$${totalPrice}"
        }

        override fun onOptionsItemSelected(item: MenuItem?): Boolean {

            when (item?.itemId) {
                android.R.id.home -> {
                    onBackPressed()
                }
            }

            return super.onOptionsItemSelected(item)
        }
    }

In this activity, we are doing three essential things, first we provide our adapter with the shopping cart items to handle, then we configure and tell our recycler view that its adapter is ready to use and we assigned it, then we compute the total price from the shopping cart items and display it.

We get a reference to the arrow back icon, and tint its color to fit our design needs.


      val upArrow = ContextCompat.getDrawable(this, R.drawable.abc_ic_ab_back_material)
            upArrow?.setColorFilter(ContextCompat.getColor(this, R.color.colorPrimary), PorterDuff.Mode.SRC_ATOP)
            supportActionBar?.setHomeAsUpIndicator(upArrow)
    override fun onOptionsItemSelected(item: MenuItem?): Boolean {
            when (item?.itemId) {
                android.R.id.home -> {
                    onBackPressed()
                }
            }
            return super.onOptionsItemSelected(item)
        }

This block of code simply redirects the user to the previous screen/activity when the back arrow icon is clicked.

Now we need a way to link our two screens, the MainActivity one and the ShoppingCartActivity one. But how? 🤔 We just need to add a click listener to the showCart button defined in the MainActivity class, the listener handler will head us to the ShoppingCartActivity screen.

So add this block of code into your MainActivity.kt file after the getProducts method:

    //..app/src/main/java/yourPackage/MainActivity.kt

    showCart.setOnClickListener {
        startActivity(Intent(this, ShoppingCartActivity::class.java))
    }

Our app is complete now with all the expected features. You can run and test it, hopefully everything should be working fine. Otherwise, check if you haven’t missed any step.

Conclusion

Finally we reached the end of this tutorial series. I think this part has been more useful to you than others because you have learned a new concept : Reactive programming and put it into practice. You can grab the source code for the last part here; if you want and you can even fork the repo to add more features to the app 😉.

Clone the project repository
  • Android
  • Kotlin
  • no pusher tech

Products

  • Channels
  • Chatkit
  • Beams

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.