1 / 20

๐Ÿ—๏ธ Design Patterns in Android

A Beginner's Guide to Clean Architecture


Build maintainable and scalable Android applications

๐Ÿค” What are Design Patterns?

Design Patterns are proven solutions to common programming problems.

Think of them as:

Why use Design Patterns in Android?

  • Make code more organized and readable
  • Easier to test and debug
  • Facilitate team collaboration
  • Handle complex UI and business logic

โŒ Common Problems in Android Development

Without proper patterns, Android apps often suffer from:

Example of Bad Code:

class MainActivity : AppCompatActivity() { // All in one place - BAD! // UI logic + Business logic + Network calls + Database override fun onCreate() { /* 200+ lines */ } }

๐Ÿ›๏ธ MVC Pattern - Model View Controller

The classic pattern that separates concerns into three components:

Model
โ†”
Controller
โ†”
View

Components:

In Android: Activities/Fragments often act as Controllers, but this can lead to bloated code.

๐Ÿ›๏ธ MVC Example - User Profile

Model (Data):

data class User( val id: Int, val name: String, val email: String ) class UserRepository { fun getUser(id: Int): User { // Fetch user from database or API return User(id, "John Doe", "john@email.com") } }

Controller (Activity):

class ProfileActivity : AppCompatActivity() { private val userRepository = UserRepository() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_profile) val user = userRepository.getUser(1) displayUser(user) // Update View } private fun displayUser(user: User) { /* Update UI */ } }

๐ŸŽญ MVP Pattern - Model View Presenter

An improvement over MVC that makes testing easier:

Model
โ†”
Presenter
โ†”
View

Key Differences from MVC:

โœ… Pros

  • Easy to test
  • Clear separation
  • Reusable presenters

โŒ Cons

  • More boilerplate
  • Memory leaks risk
  • Presenter can grow large

๐ŸŽญ MVP Example - Login Screen

1. View Contract (Interface):

interface LoginContract { interface View { fun showProgress() fun hideProgress() fun showError(message: String) fun navigateToHome() } interface Presenter { fun login(email: String, password: String) fun destroy() } }

2. Presenter Implementation:

class LoginPresenter( private val view: LoginContract.View, private val authRepository: AuthRepository ) : LoginContract.Presenter { override fun login(email: String, password: String) { view.showProgress() authRepository.login(email, password) { success -> view.hideProgress() if (success) view.navigateToHome() else view.showError("Login failed") } } }

๐Ÿ—๏ธ MVVM Pattern - Model View ViewModel

Google's recommended pattern for Android development:

Model
โ†”
ViewModel
โ†”
View

Key Features:

Best Choice for Android: Google provides extensive support with Architecture Components!

๐Ÿ—๏ธ MVVM Example - Simple Counter

1. ViewModel:

class CounterViewModel : ViewModel() { private val _count = MutableLiveData<Int>() val count: LiveData<Int> = _count init { _count.value = 0 } fun increment() { _count.value = (_count.value ?: 0) + 1 } fun decrement() { _count.value = (_count.value ?: 0) - 1 } }

2. Activity (View):

class MainActivity : AppCompatActivity() { private lateinit var viewModel: CounterViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel = ViewModelProvider(this)[CounterViewModel::class.java] // Observe data changes viewModel.count.observe(this) { count -> countTextView.text = count.toString() } incrementButton.setOnClickListener { viewModel.increment() } } }

๐Ÿ“š Repository Pattern

A pattern that centralizes data access logic:

Purpose:

  • Single source of truth for data
  • Abstracts data sources (API, Database, Cache)
  • Makes testing easier
  • Handles offline/online scenarios

Repository Implementation:

class UserRepository( private val apiService: ApiService, private val database: UserDao ) { suspend fun getUsers(): List<User> { return try { // Try to get fresh data from API val users = apiService.getUsers() database.insertUsers(users) // Cache locally users } catch (e: Exception) { // Fallback to cached data database.getAllUsers() } } }

๐Ÿ‘‘ Singleton Pattern

Ensures only one instance of a class exists throughout the app:

Use Cases: Database connections, API clients, App preferences

Thread-Safe Singleton in Kotlin:

class DatabaseManager private constructor() { companion object { @Volatile private var INSTANCE: DatabaseManager? = null fun getInstance(): DatabaseManager { return INSTANCE ?: synchronized(this) { INSTANCE ?: DatabaseManager().also { INSTANCE = it } } } } fun executeQuery(query: String) { // Database operations } } // Usage val db = DatabaseManager.getInstance()

Modern Kotlin Way (Object):

object PreferencesManager { fun saveUserToken(token: String) { /* Save token */ } fun getUserToken(): String? { /* Get token */ } }

๐Ÿ‘๏ธ Observer Pattern

Allows objects to notify multiple observers about state changes:

Android Examples:

Custom Observer Example:

interface NetworkObserver { fun onNetworkConnected() fun onNetworkDisconnected() } class NetworkManager { private val observers = mutableListOf<NetworkObserver>() fun addObserver(observer: NetworkObserver) { observers.add(observer) } fun notifyConnected() { observers.forEach { it.onNetworkConnected() } } } // Usage in Activity networkManager.addObserver(object : NetworkObserver { override fun onNetworkConnected() { // Update UI } })

๐Ÿญ Factory Pattern

Creates objects without specifying their exact class:

Fragment Factory Example:

sealed class FragmentType { object Home : FragmentType()