On this page
Design patterns are named, reusable solutions to recurring design problems. Kotlin removes the boilerplate many of them needed in Java, but the ideas remain essential, especially for reading codebases and discussing designs in reviews and interviews.
Patterns at a glance
| Pattern | Problem it solves | Where Android uses it |
|---|---|---|
| Observer | Notify many listeners of changes | Flow, StateFlow, LiveData, Compose state |
| Repository | Hide data sources behind one API | Data layer |
| Builder | Construct complex objects step by step | Retrofit.Builder, NotificationCompat.Builder, Room.databaseBuilder |
| Factory | Centralise creation logic | ViewModelProvider.Factory, Hilt @Provides |
| Singleton | One shared instance | Kotlin object, Hilt @Singleton |
| Adapter | Make incompatible interfaces work together | RecyclerView.Adapter, Retrofit CallAdapter, callback โ suspend wrappers |
| Decorator | Add behaviour by wrapping | OkHttp interceptors, Compose Modifiers, by delegation |
| Strategy | Swap algorithms at runtime | Sorting comparators, payment handlers, retry policies |
| Facade | Simple interface over a complex subsystem | Media3 player, your own SDK wrappers |
| State | Behaviour depends on state | Sealed UiState with when, MVI reducers |
Observer
class CartStore {
private val _items = MutableStateFlow<List<CartItem>>(emptyList())
val items: StateFlow<List<CartItem>> = _items.asStateFlow() // observers subscribe here
fun add(item: CartItem) = _items.update { it + item } // all observers notified
}Unlike classic listener lists, Flow ties subscriptions to coroutine scopes, so forgetting to unregister is much harder.
Builder, and when Kotlin replaces it
// Java-style builder
val request = Request.Builder().url(url).header("Accept", "application/json").build()
// Kotlin: named + default arguments do the same for your own classes
data class UploadRequest(val uri: Uri, val compress: Boolean = true, val maxSizeMb: Int = 10)
val upload = UploadRequest(uri = photo, maxSizeMb = 5)Factory
interface FeedParser { fun parse(raw: String): List<FeedItem> }
object FeedParserFactory {
fun create(contentType: String): FeedParser = when {
contentType.contains("json") -> JsonFeedParser()
contentType.contains("xml") -> RssFeedParser()
else -> error("Unsupported content type: $contentType")
}
}Adapter
suspend fun LegacyAuthClient.signInSuspending(email: String, password: String): Session =
suspendCancellableCoroutine { cont ->
signIn(email, password, object : AuthCallback {
override fun onSuccess(session: Session) = cont.resume(session)
override fun onError(error: Exception) = cont.resumeWithException(error)
})
}Decorator
class AuthInterceptor(private val tokens: TokenProvider) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
.header("Authorization", "Bearer ${tokens.current()}")
.build()
return chain.proceed(request)
}
}Compose's Modifier chain is also a decorator chain: each modifier wraps the next. Kotlin class delegation (by) makes decorators of interfaces trivial. See Kotlin Delegation.
Strategy
enum class SortOption(val comparator: Comparator<Product>) {
PRICE_LOW(compareBy { it.pricePaise }),
PRICE_HIGH(compareByDescending { it.pricePaise }),
RATING(compareByDescending<Product> { it.rating }.thenBy { it.name }),
}
fun List<Product>.sorted(option: SortOption) = sortedWith(option.comparator)Facade
interface Analytics {
fun screen(name: String)
fun event(name: String, params: Map<String, Any> = emptyMap())
}
// One implementation talks to the analytics SDK(s); the app sees only these two methods.Singleton: use with care
Kotlin's object makes singletons trivial, which makes them easy to overuse. Global singletons with state hurt testability. Prefer DI-scoped singletons (@Singleton in Hilt), which can be replaced in tests.
Choosing patterns
Many variants of behaviour
Strategy (with DI multibinding if the variants are pluggable).
Ugly third-party API
Facade or Adapter, so the rest of the app stays clean and the SDK is replaceable.
Cross-cutting behaviour
Decorator: logging, caching and auth headers without touching core logic.
Frequently asked questions
Are design patterns outdated in Kotlin?
Some implementations are simpler (builders, singletons, decorators), but the concepts remain useful vocabulary and design tools.
Is MVVM a design pattern?
It's an architectural pattern: a larger-scale structure built from smaller patterns such as Observer.
Which patterns come up in Android interviews?
Repository, Observer, Singleton (and its pitfalls), Factory, Builder, Adapter and Decorator, usually with an Android example.
Conclusion
Android code is full of design patterns: Observer in Flow, Builder in SDK setup, Decorator in interceptors and modifiers, Adapter around callbacks, Strategy for interchangeable behaviour, and Facade over complex SDKs. Use Kotlin features to implement them lightly, and name them to communicate clearly.
Next step: apply these tools to old code in How to Refactor Legacy Android Applications.
Official references
๐ฑ Apps built by Yunok Tech
Real Android apps on Google Play, free to download.
Walzi4K wallpapers for lock and home screenGet it โธ
NotiVaultNotification history: recover dismissed notificationsGet it โธ
Ready to build real Android applications?
Architecture makes the most sense when you apply it to a real project and see why each boundary exists. Learn how maintainable Android applications are designed by building one.
Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.
Explore the Android course and take your next step as an Android developer.