Object in Kotlin and the Singleton Pattern
Learn how to use the object keyword in Kotlin to define singleton, companion and anonymous objects and to ensure Java interoperability. By Caio Fukelmann Landau.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Object in Kotlin and the Singleton Pattern
20 mins
- Getting Started
- Using Singletons in Kotlin
- Using Object to Define a Shopping Cart Singleton
- Creating a Singleton’s Public Interface
- Accessing the Singleton’s Public Interface
- Working With Companion Objects
- Defining the Companion Object
- Accessing the Companion Object to Start the Activity
- Displaying Products in the Shopping Cart
- Accessing Kotlin Singletons with Java to Calculate Price
- Showing Products in the Shopping Cart
- Defining Object Listeners
- Listening to Cart Changes: Anonymous Objects
- Best Practices for Singletons and Companion Objects
- Where to Go From Here?
Working With Companion Objects
To create an Intent
to launch ShoppingCartActivity
with, you’ll define a companion object.
Companion objects are similar to standalone singleton objects like ShoppingCart
but with one key difference: The companion object belongs to its containing class.
This will be important when you learn how to access the companion objects in the next section. For now, you’ll focus on defining the companion object.
Defining the Companion Object
To define the companion object, open ShoppingCartActivity
and find the // TODO
just before the last closing bracket. Replace // TODO
with the following:
companion object {
fun newIntent(context: Context): Intent {
return Intent(context, ShoppingCartActivity::class.java)
}
}
If Android Studio prompts you to do so, import android.content.Context
and android.content.Intent
for Context
and Intent
, respectively.
Congratulations, you’ve defined a companion object!
Like the singleton you defined earlier, the companion object also contains a public function — in this case, newIntent()
. The companion object is also a single instance shared throughout your project.
Unlike in ShoppingCart
, the companion object belongs to its containing class. So to access the companion object, you need to reference it by the name of its containing class, not its direct name like you do with a singleton object. You’ll learn how to do this in the next section.
Accessing the Companion Object to Start the Activity
At this point, you’ve laid the groundwork for shopping. You’ve defined a companion object that provides an Intent
to open the shopping activity when a user clicks Go to Cart. But you have yet to start the ShoppingCartActivity
.
To start it, simply do the following:
- Open
MainActivity
and findgoToCart()
. - Replace
// TODO
with the following code:
val intent = ShoppingCartActivity.newIntent(this)
startActivity(intent)
Note that you’re referencing the companion object by using the name of its containing class: ShoppingCartActivity
. You’re also using the Intent
, which the companion object created, to start the activity.
Now, build and run. Then, click Go to Cart and you’ll see that the app will show a new, empty screen:
But where are all those products you had in your cart? Don’t worry. You’ll work on showing that screen next.
Displaying Products in the Shopping Cart
Next, you’ll build a shopping cart screen with three components:
- A TextView with the total price of the selected products.
- A list with products the user added to the cart.
- A Clear Cart button that will, you guessed it, clear the shopping cart.
First, you’ll use a Java class to calculate the cart’s total price. In the process, you’ll learn how the interoperability between Kotlin singletons and Java works.
Accessing Kotlin Singletons with Java to Calculate Price
Start by creating a new Java class to perform those calculations using the following steps:
- In Android Studio, go to File ▸ New ▸ Java Class.
- Name the class ShoppingCartCalculator and select Class.
- Press Enter, then OK
Android Studio will create a ShoppingCartCalculator
Java class file. Next, add the following function before the closing bracket:
Integer calculateTotalFromShoppingCart() {
List<Product> products = ShoppingCart.INSTANCE.getProducts();
int totalPriceCents = 0;
for (Product product : products) {
totalPriceCents += product.getPriceCents();
}
return totalPriceCents;
}
The function above uses ShoppingCart.INSTANCE
to access the singleton instance and then calculates the total by adding the cost of all the products in the cart. This is different than using the singleton in Kotlin where you don’t need to use INSTANCE
.
While the function above works, you can also clean it up a little by using @JvmStatic
, like so:
After you’ve added the annotation, the products
declaration should look like this:
- Open
ShoppingCart
. - Find
var products
. - Add
@JvmStatic
to the line above theproducts
declaration.After you’ve added the annotation, the
products
declaration should look like this:@JvmStatic var products: List = emptyList() private set
@JvmStatic
var products: List = emptyList()
private set
Now, you can remove the INSTANCE
when referencing the products. To do this, delete INSTANCE
from ShoppingCartCalculator.java. The function should now look like this:
Integer calculateTotalFromShoppingCart() {
// Removed INSTANCE below. Rest is identical:
List products = ShoppingCart.getProducts();
int totalPriceCents = 0;
for (Product product : products) {
totalPriceCents += product.getPriceCents();
}
return totalPriceCents;
}
In Java, using @JvmStatic
effectively transforms the singleton instance property into a static field in the ShoppingCart
class.
In other words, in Java, what used to be inside an object called ShoppingCart.INSTANCE
is now a top-level static field in ShoppingCart
. You can use the trick you learned earlier in this tutorial to inspect the equivalent Java code for ShoppingCart
to see how this works in action:
public final class ShoppingCart {
@NotNull
private static List products;
@NotNull
public static final ShoppingCart INSTANCE;
// ...
}
Showing Products in the Shopping Cart
You’ll need to connect everything to show the products plus the total price in the shopping cart. To do this, you:
- Open
ShoppingCartActivity
and findsetupProducts()
. - Replace the entire body of this function with the following code:
// 1.
products = ShoppingCart.products
// 2.
val calculator = ShoppingCartCalculator()
val totalPriceCents =
calculator.calculateTotalFromShoppingCart()
// 3.
viewBinding.textTotalCartValue.text =
getString(R.string.text_total_price,
totalPriceCents.asPriceString)
Here’s what the code above does:
- Reads the products from
ShoppingCart
. Singletons are single instances throughout your app. Therefore, the code will contain all the products the user added to the shopping cart. - Creates an instance of the Java class you defined earlier,
ShoppingCartCalculator
, and uses it to calculate the total cost of all the items in the shopping cart. The Java method reads the Kotlin singleton internally and returns the total price. The same singleton object is read by both the Java code in the calculator and above in the Kotlin code. - Updates the TextView that displays the total price, converting it into a currency format using the Kotlin extension defined in StringUtils.kt.
Now, build and run. Test that it’s working by adding some products to the cart, then clicking Go to Cart. You’ll see a screen that shows the shopping cart with the products plus the total price:
You just learned how to get Java to access the data from a Kotlin singleton. Next, you’ll learn how to clear the products in the cart using the Clear Cart button.