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:
- ๐งฉTemplates: Reusable blueprints for solving common problems
- ๐บ๏ธBest Practices: Time-tested approaches used by experienced developers
- ๐งTools: Solutions that make code more organized and maintainable
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:
- ๐Spaghetti Code: Everything mixed together in Activities/Fragments
- ๐งชHard to Test: Business logic tightly coupled with UI
- ๐Memory Leaks: Activities not properly managed
- ๐ฑConfiguration Changes: Data loss on screen rotation
- ๐ธ๏ธTight Coupling: Components depend too much on each other
Example of Bad Code:
class MainActivity : AppCompatActivity() {
override fun onCreate() { }
}
๐๏ธ MVC Pattern - Model View Controller
The classic pattern that separates concerns into three components:
Model
โ
Controller
โ
View
Components:
- ๐Model: Data and business logic
- ๐๏ธView: UI components (Activities, Fragments, Layouts)
- ๐ฎController: Handles user input and coordinates Model-View
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 {
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)
}
private fun displayUser(user: User) { }
}
๐ญ MVP Pattern - Model View Presenter
An improvement over MVC that makes testing easier:
Model
โ
Presenter
โ
View
Key Differences from MVC:
- ๐ญPresenter: Handles ALL business logic (not the View)
- ๐ฑView: Just displays data, no business logic
- ๐Interface: View implements an interface for testing
โ
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:
- ๐Data Binding: Automatic UI updates when data changes
- ๐๏ธViewModel: Survives configuration changes
- ๐๏ธObservable: LiveData/StateFlow for reactive programming
- ๐Lifecycle Aware: Automatically handles Activity/Fragment lifecycle
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]
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 {
val users = apiService.getUsers()
database.insertUsers(users)
users
} catch (e: Exception) {
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) {
}
}
val db = DatabaseManager.getInstance()
Modern Kotlin Way (Object):
object PreferencesManager {
fun saveUserToken(token: String) { }
fun getUserToken(): String? { }
}
๐๏ธ Observer Pattern
Allows objects to notify multiple observers about state changes:
Android Examples:
- ๐ฑLiveData: Observes data changes in MVVM
- ๐Event Listeners: Button clicks, text changes
- ๐Broadcast Receivers: System events
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() }
}
}
networkManager.addObserver(object : NetworkObserver {
override fun onNetworkConnected() {
}
})
๐ญ Factory Pattern
Creates objects without specifying their exact class:
Fragment Factory Example:
sealed class FragmentType {
object Home : FragmentType()