Build a shopping cart with Kotlin - Part 1: Listing products
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.
7 March 2019
by Ethiel Adiassa