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?
Defining Object Listeners
When the user clears the shopping cart, ShoppingCartActivity
should update to display an empty cart instead of one with products. But, singletons don’t automatically notify their listeners about changes, so you’ll need to implement this behavior to enable a user to clear their cart.
Notifying Listeners of Cart Changes
To notify listeners about cart changes, open ShoppingCart and add the following code before the last closing bracket:
interface OnCartChangedListener {
fun onCartChanged()
}
Here, you’re defining an interface that ShoppingCart
will use to notify listeners that its data has changed.
Next, add the following code between var products
and addProduct()
in ShoppingCart
:
private var onCartChangedListener: WeakReference<OnCartChangedListener>? = null
fun setOnCartChangedListener(listener: OnCartChangedListener) {
this.onCartChangedListener = WeakReference(listener)
}
If Android Studio prompts you to import, import java.lang.ref.WeakReference
for WeakReference
.
In the code above, you defined a weak reference to a listener. This makes the ShoppingCart
notify that listener whenever data changes.
The weak reference prevents the singleton from strongly holding on to an activity, which could cause a memory leak. More about that later!
Now that you have a listener, you have to notify it! While still in ShoppingCart
, add the following function:
private fun notifyCartChanged() {
onCartChangedListener?.get()?.onCartChanged()
}
This adds a private function to your singleton. It notifies your listener that the product list changed. Because only the singleton can update the list, only the singleton should be able to trigger this notification. Private functions like this will not be visible from outside the singleton, so they’re perfect for internal logic.
And now, add a call to this function at the end of both addProduct()
and clear()
. When you’re done tt should look like this:
fun addProduct(product: Product) {
products = products + listOf(product)
notifyCartChanged() // New
}
fun clear() {
products = emptyList()
notifyCartChanged() // New
}
Now, whenever the user adds a product to the cart or clears their cart, ShoppingCart
will notify its listener.
Listening to Cart Changes: Anonymous Objects
Here, you’ll use yet another form of the object
keyword! This time, you’ll define an anonymous object that implements the interface you defined earlier.
To do this, go back to ShoppingCartActivity
and add the following property between var products
and onCreate()
:
private var onCartChangedListener =
object : ShoppingCart.OnCartChangedListener {
override fun onCartChanged() {
setupProducts()
setupRecyclerView()
}
}
Since this interface has a function called onCartChanged()
, you implemented it right in your object declaration!
You defined this anonymous object as a property of your ShoppingCartActivity. This means that the overridden onCartChanged()
can access any functions and properties in the activity.
With that in mind, you call a couple of functions, setupProducts()
and setupRecyclerView()
, from the activity when the cart changes. These functions will trigger a re-rendering of RecyclerView
and the total price.
Now, you need to tell the singleton to use the property you just created as its listener. Find // Your code
inside onCreate()
and replace it with:
ShoppingCart.setOnCartChangedListener(onCartChangedListener)
This tells your singleton to call onCartChangedListener
when the cart changes.
Next, find setupClearCartButton()
and replace // TODO
with:
viewBinding.clearCartButton.setOnClickListener {
ShoppingCart.clear()
}
The code above calls clear()
in the singleton when the user taps the Clear Cart button.
When the user clears their shopping cart, the singleton notifies its listener. Since the listener has been set to the anonymous object in ShoppingCartActivity, that’s what gets notified.
Now, build and run your app. Add some products to the shopping cart, click Go to Cart and Clear Cart. This will clear the cart and update the view:
And there you have it: the final form of your shopping cart experience. :]
Next, you’ll learn some important best practices to consider when working with Kotlin objects.
Best Practices for Singletons and Companion Objects
Before you finish the tutorial, take a moment to review some best practices.
- Avoid overusing singletons: It’s tempting to use singletons as a solution for all your data sharing needs. While handy at first, overusing singletons will cause maintainability issues because many parts of your code will suddenly depend on a singleton. You’ll find that making one change will affect several unrelated parts of your project. Use singletons sparingly to save yourself this headache.
- Singletons can cause trouble with memory usage: Avoid having too much data living in singletons. Remember, they’re global, and garbage collection will never automatically deallocate data held strongly by a singleton.
- Singletons can cause memory leaks: When you use a singleton to reference an instance used externally, leaks can happen. This applies especially to Android-related classes like activities, fragments, adapters and more. If your singleton keeps a strong reference to any of these, the garbage collector won’t deallocate them and they’ll stay in memory indefinitely. To avoid this issue, restructure your code so singletons either don’t hold instances of those classes, or use weak references.
Where to Go From Here?
Download the final project using the Download Materials button at the top or bottom of this tutorial.
In this tutorial, you learned important uses of the object
keyword that you can apply in your next Android app.
To learn more uses of object
not described here, read the official Kotlin documentation.
And remember, singletons are not a persistence solution! They only live for as long as your app is in memory. To learn about data storage solutions, check out our other tutorials and video courses:
- DataStore Tutorial
- Data Persistence With Room Tutorial
- Saving Data on Android Video Course
- Android DataStore Video Course
For more information about Android and memory leaks, read our Memory Leaks in Android Tutorial.
We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!