🎉 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 1: Listing products

  • Ethiel Adiassa

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

Introduction

In this tutorial I’ll show you how to build a shopping cart using Kotlin. A shopping cart is a must-have in any e-commerce application. This course will guide you through building a full shopping cart with Kotlin.

Our end result will be an Android app built with the Kotlin language (rather than Java for the sake of productivity) listing some products to the user and allowing them to add some to their shopping cart. They should also be able to review their shopping cart.

In this first part of this tutorial series we’ll be building our app that shows a list of products to the users.

Demo

Here is the final result of the first part of the tutorial series 😎

Prerequisites

In order to follow along, you will need some experience with the Kotlin programming language. You will also need appropriate IDEs. I suggest IntelliJ IDEA or Android Studio. It is also assumed that you know how to use the IDEs that you are working with, including interacting with either an emulated or physical mobile device for running your apps.

Configuring your project

Open Android Studio, create a new project. Insert the name of your app and company domain name then select the Include Kotlin support checkbox to enable Kotlin in the project.

For this article, we will set the minimum supported Android version at 4.03 (API 15). Next, choose an empty activity template and click on Finish.

Then head over to your ../app/build.gradle file and paste this inside the dependencies block, as we’ll be using these dependencies in this tutorial

    //..app/build.gradle
    implementation 'com.google.code.gson:gson:2.8.2'
    implementation 'com.squareup.picasso:picasso:2.71828'
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
    implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.android.support:cardview-v7:28.0.0'
    implementation 'com.android.support:recyclerview-v7:28.0.0'
  • Retrofit: We will need the Retrofit library (a “type-safe HTTP client”) to enable us send messages to our remote server which we will build later on.
  • Picasso: Picasso is "A powerful image downloading and caching library for Android”

Also, amend your styles.xml like the following. This should enable us to use a toolbar inside our application.

    //..app/src/main/res/values/styles.xml

    <resources>

        <!-- Base application theme. -->
        <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
            <!-- Customize your theme here. -->
            <item name="colorPrimary">@color/colorPrimary</item>
            <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
            <item name="colorAccent">@color/colorAccent</item>
        </style>

    </resources>

Build your product model, adapter and layout

Our product should have a unique identifier, a price, a name, a description and a set of images if possible. Now that we know the structure of our product item, let’s define its model. We’ll build our product entity using a Kotlin data class.

Create a Product.kt file, then copy and paste the following piece of code inside:

    //..app/src/main/java/yourPackage/Product.kt
    import com.google.gson.annotations.SerializedName

    data class Product(
        @SerializedName("description")
        var description: String? = null,

        @SerializedName("id")
        var id: Int? = null,

        @SerializedName("name")
        var name: String? = null,

        @SerializedName("price")
        var price: String? = null,

        @SerializedName("photos")
        var photos: List<Photo> = arrayListOf()
    )

As our product has a set of photos, we’ll also define its entity. Create a Photo.kt file, then paste the following code inside as well:

    //..app/src/main/java/yourPackage/Photo.kt
    import com.google.gson.annotations.SerializedName

    data class Photo(
        @SerializedName("filename")
        var filename: String? = null
    )

Next, we’ll build our product adapter responsible to handle the display of our products list.

Create a ProductAdapter.kt file and paste the following inside:

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

    import android.annotation.SuppressLint
    import android.content.Context
    import android.support.design.widget.Snackbar
    import android.support.v7.widget.RecyclerView
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import android.widget.Toast
    import com.squareup.picasso.Picasso
    import kotlinx.android.synthetic.main.activity_main.*
    import kotlinx.android.synthetic.main.product_row_item.view.*

    class ProductAdapter(var context: Context, var products: List<Product> = arrayListOf()) :
        RecyclerView.Adapter<ProductAdapter.ViewHolder>() {
        override fun onCreateViewHolder(p0: ViewGroup, p1: Int): ProductAdapter.ViewHolder {
            // The layout design used for each list item
            val view = LayoutInflater.from(context).inflate(R.layout.product_row_item, null)
            return ViewHolder(view)

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

        override fun onBindViewHolder(viewHolder: ProductAdapter.ViewHolder, position: Int) {
         //we simply call the `bindProduct` function here
            viewHolder.bindProduct(products[position])
        }

        class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {

            // This displays the product information for each item
            fun bindProduct(product: Product) {

                itemView.product_name.text = product.name
                itemView.product_price.text = "$${product.price.toString()}"
                Picasso.get().load(product.photos[0].filename).fit().into(itemView.product_image)
            }

        }

    }

Next, let’s create our product item layout. This layout file contains:

  • an ImageView to display the product image
  • two TextView one to display the product name and the other for the product price.

All these widgets are wrapped inside a CardView to add a shadow and a radius to the layout 🙂.

Create a product_row_item file and paste the following inside. This layout is responsible for handling the view of a single item of our list.

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

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.v7.widget.CardView
            xmlns:card_view="http://schemas.android.com/tools"
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            app:cardUseCompatPadding="true"
            android:layout_margin="4dp"
            app:cardBackgroundColor="@android:color/white"
            app:cardCornerRadius="4dp"
            android:background="?attr/selectableItemBackground"
            app:cardElevation="3dp"
            android:foreground="?attr/selectableItemBackground"
            card_view:cardElevation="4dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

        <LinearLayout
                android:orientation="vertical"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

            <ImageView
                    android:id="@+id/product_image"
                    android:layout_width="match_parent"
                    android:layout_height="140dp"/>

            <LinearLayout
                    android:padding="10dp"
                    android:orientation="vertical"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">


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


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

            <ImageButton
                    android:id="@+id/addToCart"
                    android:paddingHorizontal="16dp"
                    android:tint="@android:color/white"
                    android:paddingVertical="4dp"
                    android:src="@drawable/ic_add_shopping"
                    android:layout_gravity="end"
                    android:background="@color/colorAccent"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    card_view:targetApi="o"/>

        </LinearLayout>

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

These are the links to get the drawable icons we used in our layout : ic_add_shopping and ic_shopping_basket. They are to paste in ../app/src/main/res/drawable folder.

Preparing API calls

We’ll make calls to an external API to get our products data.

Create an APIConfig.kt file. This class gives us an instance of Retrofit for our network calls:

    //..app/src/main/java/yourPackage/APIConfig.kt
    import android.content.Context
    import okhttp3.OkHttpClient
    import retrofit2.Retrofit
    import retrofit2.converter.gson.GsonConverterFactory
    import com.google.gson.GsonBuilder
    import retrofit2.converter.scalars.ScalarsConverterFactory

    object APIConfig {

        val BASE_URL = "https://all-spices.com/api/products/"

        private var retrofit: Retrofit? = null

        var gson = GsonBuilder()
            .setLenient()
            .create()

        fun getRetrofitClient(context: Context): Retrofit {

            val okHttpClient = OkHttpClient.Builder()
                .build()

            if (retrofit == null) {
                retrofit = Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .client(okHttpClient)
                            .addConverterFactory(ScalarsConverterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .build()
            }
            return retrofit!!
        }
    }

Next, create an API Interface file in the src/main/java/yourPackage folder called ApiService.kt. This interface is used to define endpoints to be used during network calls. For this application, we will create just one endpoint:

    import retrofit2.Call
    import retrofit2.http.*

    interface APIService {
        @Headers("Content-Type: application/json", "Accept: application/json")
        @GET("bestRated")
        fun getProducts(
        ): Call<List<Product>>

    }

Listing products

For listing products, we’ll need a recycler view (a recycler view is a widget for listing a list of items, as it happens our products list). Now, move on to your src/main/java/res/layout/activity_main.xml file, amend it like the following:

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:layout_height="match_parent"
            android:background="#fffffa"
            android:id="@+id/coordinator"
            android:layout_width="match_parent">


        <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 List"
                    android:background="@android:color/white"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize">

            </android.support.v7.widget.Toolbar>

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


        <android.support.v4.widget.SwipeRefreshLayout
                android:id="@+id/swipeRefreshLayout"
                app:layout_behavior="@string/appbar_scrolling_view_behavior"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

            <android.support.v7.widget.RecyclerView
                    android:id="@+id/products_recyclerview"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"/>

        </android.support.v4.widget.SwipeRefreshLayout>


        <android.support.design.widget.FloatingActionButton
                android:id="@+id/showBasket"
                android:src="@drawable/ic_shopping_basket"
                android:tint="@android:color/white"
                android:layout_margin="16dp"
                android:layout_gravity="bottom|end"
                app:fabSize="normal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

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

The above layout contains a recycler view which itself is wrapped in a SwipeRefreshLayout widget. We also add a button for adding items to our shopping cart, but this will be handled in the second part of the tutorial.

Next, move on to your src/main/MainActvity.kt file, and amend like the following:

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

    import android.content.Intent
    import android.support.v7.app.AppCompatActivity
    import android.os.Bundle
    import android.support.v4.content.ContextCompat
    import android.support.v7.widget.StaggeredGridLayoutManager
    import android.util.Log
    import android.widget.Toast
    import kotlinx.android.synthetic.main.activity_main.*
    import retrofit2.Call
    import retrofit2.Response

    class MainActivity : AppCompatActivity() {

        private lateinit var apiService: APIService
        private lateinit var productAdapter: ProductAdapter

        private var products = listOf<Product>()

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)

            setContentView(R.layout.activity_main)

            setSupportActionBar(toolbar)
            apiService = APIConfig.getRetrofitClient(this).create(APIService::class.java)

            swipeRefreshLayout.setColorSchemeColors(ContextCompat.getColor(this, R.color.colorPrimary))

            swipeRefreshLayout.isRefreshing = true

            // assign a layout manager to the recycler view
            products_recyclerview.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)

            getProducts()

        }


        fun getProducts() {
            apiService.getProducts().enqueue(object : retrofit2.Callback<List<Product>> {
                override fun onFailure(call: Call<List<Product>>, t: Throwable) {

                    print(t.message)
                    Log.d("Data error", t.message)
                    Toast.makeText(this@MainActivity, t.message, Toast.LENGTH_SHORT).show()

                }

                override fun onResponse(call: Call<List<Product>>, response: Response<List<Product>>) {

                    swipeRefreshLayout.isRefreshing = false
                    products = response.body()!!

                    productAdapter = ProductAdapter(this@MainActivity, products)

                    products_recyclerview.adapter = productAdapter
                    productAdapter.notifyDataSetChanged()

                }

            })
        }

    }

We declared an APIService instance, a ProductAdapter instance we’ll initialize later. Next, we initialized the list to hold the products: private var products = listOf<Product>(). Then, in the onCreate method, we initialized our APIService instance, configured our swipe refresh layout and made it refreshing, and assigned a proper layout to our recycler view.

In the getProducts methods, we made an API call to fetch our products, if everything gets well we first disable the swipe refresh layout, then assign the result to our products list, initialised our product adapter, assigned the adapter to the recycler view, and tell the adapter data its state has changed. Otherwise, we just logged the error for debugging purpose.

Next up is to add the Internet permission in your AndroidManifest.xml file. Update the file with the code snippet below:

    //app/src/main    
        <uses-permission android:name="android.permission.INTERNET"/>

We are done with the first part of this article. Now you can run your app to see if everything is correct.

Conclusion

In this first tutorial of this series, we have demonstrated how to get data/products from an external API, and render it with the help of a recycler view. In the next part, we’ll see how to add products to cart. This is the code for the first of the tutorial series.

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.