Design patterns are proven solutions to common problems in software design. They help structure code, improve reusability, and maintain consistency. Below, we explore various design patterns and their applications in Android development.
This pattern ensures a single instance of a class. Common use cases in Android include SharedPreferences
, Retrofit
, and Room database instances.
Example:
object RetrofitInstance {
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.build()
}
The Builder Pattern helps construct complex objects step by step. In Android, AlertDialog.Builder
or Notification.Builder
are great examples.
Example:
val alertDialog = AlertDialog.Builder(this)
.setTitle("Title")
.setMessage("Message")
.setPositiveButton("OK", null)
.create()
alertDialog.show()
The Observer Pattern is used to notify observers of changes. In Android, LiveData
is commonly used.
Example:
viewModel.liveData.observe(this) { data ->
textView.text = data
}
This pattern abstracts data sources and provides a clean API. It’s widely used with Room and Retrofit.
Example:
class Repository(private val api: ApiService, private val dao: Dao) {
fun fetchData() = api.getData()
fun saveData(data: List<Data>) = dao.insertAll(data)
}
Combine Repository and UseCase patterns for better separation of concerns.
Example:
class GetUserUseCase(private val repository: UserRepository) {
fun execute() = repository.getUser()
}
The Factory Pattern is used to create objects without specifying the exact class. In Android, FragmentFactory
can be used to dynamically create fragments.
Example:
class CustomFragmentFactory : FragmentFactory() {
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
return when (className) {
MyFragment::class.java.name -> MyFragment(arguments)
else -> super.instantiate(classLoader, className)
}
}
}
The Prototype Pattern involves cloning objects to avoid expensive object creation. In Android, you can use this pattern for UI elements.
Example:
val originalView = TextView(context)
val clonedView = originalView.copy()
This pattern converts one interface into another. The RecyclerView.Adapter
is a common example in Android.
Example:
class MyAdapter(private val data: List<String>) : RecyclerView.Adapter<MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_view, parent, false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(data[position])
}
override fun getItemCount() = data.size
}
The Facade Pattern provides a simplified interface to a larger, more complex system. MediaPlayer
in Android is a great example.
Example:
val mediaPlayer = MediaPlayer().apply {
setDataSource("audio_file.mp3")
prepare()
start()
}
This pattern dynamically adds functionality to an object. Wrapping InputStream
in BufferedInputStream
is an example.
Example:
val inputStream = FileInputStream(file)
val bufferedStream = BufferedInputStream(inputStream)
The Bridge Pattern decouples abstraction from implementation. In Android, it can be seen in custom views.
Example:
class CustomView(context: Context) : View(context) {
override fun onDraw(canvas: Canvas) {
// Custom drawing logic
}
}
This pattern composes objects into tree structures. The ViewGroup
and View
hierarchy in Android are examples.
Example:
val linearLayout = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
addView(TextView(context))
addView(Button(context))
}
This pattern defines a family of algorithms and lets you choose one at runtime. In Android, it is used for handling different UI states.
Example:
val strategy = if (isTablet) TabletLayoutStrategy() else PhoneLayoutStrategy()
strategy.applyLayout()
The Command Pattern encapsulates user actions as objects. Button clicks in Android can follow this pattern.
Example:
val command = ClickCommand(Button(context))
command.execute()
The Memento Pattern saves an object’s state. SavedStateHandle
in ViewModel is an example.
Example:
class MyViewModel(private val state: SavedStateHandle) : ViewModel() {
val data = state.getLiveData("key")
}
The State Pattern manages state-dependent behavior. In Android, it can be used for media player states.
Example:
class MediaPlayerState {
fun play() { /* ... */ }
fun pause() { /* ... */ }
}
Simplify dependency lookup for services like LocationManager
or NotificationManager
.
Example:
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
Note: You don’t have to memorize all these patterns by heart. Focus on understanding the core principles and applying them when needed in your projects. It’s a journey, not a race!
Akshay Nandwana
Founder AndroidEngineers
You can connect with me on:
Join our upcoming classes
https://www.androidengineers.in/courses