Structural Design Patterns

Kotlin Design Patterns: Flyweight

Michal Ankiersztajn
ProAndroidDev
Published in
3 min readMay 1, 2024

--

Purpose of the pattern

The pattern is mostly used to balance memory usage in your app. It’s all about object reusability (note that I’ve used the word Object, not Class). Instead of creating new objects each time, you have a pool of similar objects that can be reused.

This way, instead of allocating memory each time the object is created, you allocate it once and reuse the previously created object.

This means you’ll save some CPU and memory in object creation and make garbage collecting faster. However, there is a tradeoff. Depending on implementation, you will have to either:

  • Control removing objects from the pool, which can be tricky as if you remove the object that is currently used, it’ll break things.
  • Allocate memory for unused objects. In this approach there is no deleting objects from the pool, which means allocating memory for something not currently used.

Both approaches have downsides, choose one depending on your needs. If something is unclear Example should make it all more understandable:

Example

Your app uses the same image throughout multiple places on the same screen. You launch your app, and … it crashes due to OutOfMemoryError . The images are already compressed, and they cannot be smaller. You’ve to figure out a way how to save more memory.

This is a perfect use case for a Flyweight say there are 3 images, each being 5 MB big, meaning they take 15 MB total, but with Flyweight they’ll only take 5 MB.

Moreover, you’re getting the images from the Web by the URL , using Flyweight here will also save time and limit internet consumption.

Let’s implement it:

Flyweight class diagram

In Flyweight we usually call the creating class Factory . Application will depend on both the Image and ImageFactory as it’ll use Image models, but create them through ImageFactory .

data class Image(val bytes: ByteArray)

class ImageFactory {
private val cache = mutableMapOf<String, Image>()

private suspend fun getImage(url: String): Image =
cache[url] ?: fetchImage(url).also { image -> cache[url] = image }
}

As you can see, the Flyweight pattern implementation is pretty simple. Here’s how to use it:

fun main() {
val factory = ImageFactory()
val scope = CoroutineScope(Dispatchers.IO)
scope.launch {
val image = factory.get("image")
}
scope.launch {
val image = factory.get("image")
}
}

In this exact example, there is a problem of synchronization

However, there is 1 catch: you make 2 requests for an image with the same URL, which is not what we want. It happens because, before the first fetchImage(url) ends, another Coroutines tries to get the URL from cache , but since it’s not yet present, it invokes fetchImage(url) as well.

We can fix it in multiple ways. I’ll show the way using Mutex that works similarly to the Flyweight itself:

class ImageFactory {
private val cache = mutableMapOf<String, Image>()
private val locks = mutableMapOf<String, Mutex>()
private val lock = Mutex()

suspend fun get(url: String): Image {
val imageMutex = lock.withLock {
locks.getOrPut(url) { Mutex() }
}

val image = imageMutex.withLock {
getImage(url)
}
locks.remove(url)
return image
}

private suspend fun getImage(url: String): Image =
cache[url] ?: fetchImage(url).also { image -> cache[url] = image }
}

Thanks for reading! Please clap and follow me for more!

More design patterns

Design Patterns In Kotlin

17 stories

Based on the book:

“Wzorce projektowe : elementy oprogramowania obiektowego wielokrotnego użytku” — Erich Gamma Autor; Janusz Jabłonowski (Translator); Grady Booch (Introduction author); Richard Helm (Author); Ralph Johnson (Author); John M Vlissides (Author)

--

--