๐Ÿ›๏ธ Android Architecture and Clean Code

Design Patterns Every Android Developer Should Know

You already use design patterns every day in Android, often without naming them: Flow is Observer, Retrofit.Builder is Builder, Hilt modules are Factories. Recognising patterns helps you read libraries faster, communicate designs, and choose solutions deliberately.

โœ๏ธ Yunok Tech ๐Ÿ“… Updated โฑ 3 min read ๐ŸŽฏ Intermediate
On this page
  1. Patterns at a glance
  2. Observer
  3. Builder, and when Kotlin replaces it
  4. Factory
  5. Adapter
  6. Decorator
  7. Strategy
  8. Facade
  9. Singleton: use with care
  10. Choosing patterns
  11. Frequently asked questions
  12. Conclusion

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

PatternProblem it solvesWhere Android uses it
ObserverNotify many listeners of changesFlow, StateFlow, LiveData, Compose state
RepositoryHide data sources behind one APIData layer
BuilderConstruct complex objects step by stepRetrofit.Builder, NotificationCompat.Builder, Room.databaseBuilder
FactoryCentralise creation logicViewModelProvider.Factory, Hilt @Provides
SingletonOne shared instanceKotlin object, Hilt @Singleton
AdapterMake incompatible interfaces work togetherRecyclerView.Adapter, Retrofit CallAdapter, callback โ†’ suspend wrappers
DecoratorAdd behaviour by wrappingOkHttp interceptors, Compose Modifiers, by delegation
StrategySwap algorithms at runtimeSorting comparators, payment handlers, retry policies
FacadeSimple interface over a complex subsystemMedia3 player, your own SDK wrappers
StateBehaviour depends on stateSealed UiState with when, MVI reducers

Observer

Flow is Observer with structured lifecyclesKotlin
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

Named arguments often replace buildersKotlin
// 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

Creating the right parserKotlin
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

Adapting a callback SDK to coroutinesKotlin
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

OkHttp interceptor decorates every requestKotlin
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

Pluggable sortingKotlin
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

Hide a complex SDK behind your own APIKotlin
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.

Patterns across a typical Android request path A ViewModel observes a StateFlow (Observer) from a Repository (Repository pattern), which uses Retrofit built with a Builder, with OkHttp interceptors as Decorators, and an analytics Facade. ViewModelObserver RepositoryRepository RetrofitBuilder ยท Adapter OkHttpDecorator chain
A single network call passes through four or five patterns. Naming them makes the design easier to discuss.

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.

See all our apps โ†’

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.

Share: LinkedIn X / Twitter WhatsApp