6 Design Patterns Every Android Developer Must Know

Muhammad Naeem
10 min readFeb 7, 2023

--

A design pattern is a general, repeatable solution to a commonly occurring problem in software design. Design patterns provide a way to describe and communicate proven and effective solutions to common problems so that other designers and developers can understand and implement them more easily.

In software engineering, a design pattern is a general reusable solution to a commonly occurring problem in software design.

Design patterns are not specific to any programming language or technology and can be applied in many different contexts. They are a way to capture and share experience and knowledge about software design and architecture.

1- Singleton pattern

The Singleton pattern is a design pattern that restricts a class to have only one instance, while providing a global point of access to this instance for other objects. This pattern works well when modeling real-world objects with only one instance. For example, if you have an object that makes network or database connections, having more than one instance of the project may cause problems and mix data. That’s why in some scenarios you want to restrict the creation of more than one instance.

The Kotlin object keyword declares a singleton without needing to specify a static instance

Properties of Singleton Class

The following are the key properties of a Singleton class:

  1. There can only be one instance of a Singleton class in the entire application.
  2. The instance of the Singleton class must be accessible from any part of the application.
  3. The instance of the Singleton class must be created only once, and subsequent calls to get the instance must return the same object.
  4. The Singleton class must provide a way for other objects to access its instance. This is typically done through a static method or property.
  5. The Singleton class must ensure that its instance is thread-safe, so that it can be used in a multi threaded environment.
  6. The Singleton class should be designed in such a way that it can be easily extended or sub-classed if needed, while still maintaining its key properties.
  7. The Singleton pattern should not be used when the number of instances of a class needs to change dynamically, or when the class requires multiple instances with different state or behavior.

Here is an example of a singleton class in Kotlin:

object MySingleton {
// Properties and methods of the singleton class
}

This will return the single instance of the MySingleton class, without needing to call a separate getInstance() method.

old Method Example

class Singleton private constructor() {
companion object {
private var instance: Singleton? = null
fun getInstance(): Singleton {
if (instance == null) {
instance = Singleton()
}
return instance!!
}
}
}

in this example, the private constructor of the Singleton class ensures that no other objects can be created from it, and the getInstance function is used to access the single instance of the class. The instance variable is declared as a companion object to make it accessible from outside the class. The !! operator is used to safely access the value of instance and prevent a possible NullPointerException.

when useful ?
The Singleton pattern is useful for scenarios where you need to ensure that there is only one instance of a class in your application, for example, a database connection or a network service.

2- Factory pattern

The Factory design pattern is a creational pattern used to create objects. It is a way of abstracting the creation of objects so that the code is more maintainable and extensible. This pattern is especially useful in Android Kotlin development, as it allows developers to create objects without having to write a lot of code.

Using the Factory design pattern helps to make code more organized and easier to read. It also makes it easier to change the implementation of objects without having to change the code everywhere it is used.

interface Shape {
fun draw()
}

class Circle: Shape {
override fun draw() {
println("Drawing a Circle")
}
}

class Square: Shape {
override fun draw() {
println("Drawing a Square")
}
}

class ShapeFactory {
fun getShape(shapeType: String): Shape? {
return when (shapeType.toLowerCase()) {
"circle" -> Circle()
"square" -> Square()
else -> null
}
}
}

In this example, the Shape interface defines a method to draw a shape. The Circle and Square classes implement the Shape interface. The ShapeFactory class is a factory class that creates objects of different shapes based on the type of shape requested.

when useful ?
The Factory pattern is useful in situations where a class cannot anticipate the type of objects it needs to create, or when a class wants to delegate the responsibility of object creation to its subclasses. It also helps to decouple the client code from the objects it creates, making the code more flexible and maintainable.

3- Builder pattern

Android Kotlin Builder Pattern is an object-oriented design pattern that allows for the construction of complex objects from smaller parts. It is a type of creational pattern, which means it is used to create objects. The builder pattern allows for the creation of objects that can be customized with different parameters or values.

The builder pattern is especially useful when dealing with complex objects that require multiple steps to create. It allows developers to create objects with different combinations of attributes, without having to write multiple constructors for each combination.

Benefits of the Builder Pattern

The Builder Pattern is a great way to create complex objects with multiple parameters. It allows developers to create objects with different combinations of attributes, without having to write multiple constructors for each combination. This reduces the amount of code that needs to be written and makes the code more readable.

The builder pattern also makes it easier to maintain the code, as it is easier to modify or add new attributes without having to rewrite the entire code. This makes it easier to keep the code up to date and to make changes quickly.

How to Implement the Builder Pattern ?

The Builder Pattern can be implemented by creating a class that defines the attributes of the object. This class should have a constructor that takes in the different parameters or values that are needed to create the object. The class should also have a method that creates the object with the given parameters.

The class should also have a builder class that contains methods for setting the different parameters or values. This builder class should then be used to create the object with the desired parameters or values.

Here are the general rules to create a Builder class:

  1. The Builder class should have a private constructor or default constructor, to ensure that it can only be instantiated by its own methods.
  2. The Builder class should have methods for setting the properties of the object being constructed, and these methods should return the Builder instance to allow method chaining.
  3. The Builder class should have a build method that returns the final object being constructed.
  4. The final object being constructed should have a private constructor or a package-private constructor that takes the Builder instance as an argument, and sets the properties of the object using the values stored in the Builder instance.
  5. The final object being constructed should provide a static method or a factory method that returns a new instance of the Builder class, to allow clients to start constructing the object.
  6. The Builder class should be immutable, meaning that once an instance of the Builder class is created, its properties should not be changed. This ensures that the state of the Builder instance is well-defined and that it can be used to construct multiple objects.

Here is an example of a builder pattern in Kotlin:

class User private constructor(val username: String,
val password: String, val email: String) {
// Properties and methods of the User class

class Builder {
private var username: String = ""
private var password: String = ""
private var email: String = ""

fun setUsername(username: String): Builder {
this.username = username
return this
}

fun setPassword(password: String): Builder {
this.password = password
return this
}

fun setEmail(email: String): Builder {
this.email = email
return this
}

fun build(): User {
return User(username, password, email)
}
}
}

In this implementation, the User class has a private constructor, and a separate Builder class is responsible for creating and configuring User objects.

The Builder class has methods for setting the various properties of the User object. These methods return the Builder object itself, which allows for method chaining.

The build() method creates and returns a new User object, using the values that were set in the Builder.

To create a new User object, you can use the Builder like this:

val user = User.Builder()
.setUsername("name here")
.setPassword("password here")
.setEmail("email@example.com")
.build()

when useful ?
The Builder pattern is useful in situations where a complex object can be created in multiple ways and its construction process is independent of the parts that make up the object and how they are assembled. It also provides a convenient way to create objects with multiple optional parameters.

4- Facade pattern

The Facade pattern is a structural design pattern that provides a simplified interface to a complex system of components. The goal of the Facade pattern is to hide the complexity of the system and provide a unified, high-level API for working with the system.

Here is an example of a Facade pattern in Kotlin:

interface UsersApi {
@GET("userss")
fun listUsers(): Call<List<User>>
}

5- Dependency Injection pattern

Dependency Injection (DI) is a software design pattern which enables the development of loosely coupled code. It allows objects to be created without being tightly coupled to their dependencies, making them easier to maintain and test.

Dependency Injection is a powerful pattern that can help developers create more robust, maintainable, and testable code. It is an important concept to understand when developing software applications.

There are two types of dependency injection: constructor injection and setter injection. In constructor injection, the dependencies are passed to the object’s constructor, while in setter injection, the dependencies are set on the object using setter methods.

By using dependency injection, you can achieve several benefits:

  1. Loose coupling: Dependency injection helps you decouple your objects, making it easier to test and maintain the code.
  2. Improved readability: By injecting dependencies into the objects, the code becomes more readable, as it’s easier to understand the purpose of each object.
  3. Increased flexibility: With dependency injection, you can change the implementation of a dependency without affecting the code that uses it.

Here’s an example of constructor injection in Kotlin:

interface Logger {
fun log(message: String)
}

class ConsoleLogger : Logger {
override fun log(message: String) {
println(message)
}
}

class UserService(private val logger: Logger) {
fun createUser(username: String) {
// Code to create a user
logger.log("User $username created")
}
}

fun main() {
val userService = UserService(ConsoleLogger())
userService.createUser("demo")
// Output: User demo created
}

In this example, the UserService class depends on a Logger instance to log messages. Instead of creating a Logger instance within the UserService class, it's provided through the constructor, making it easier to change the implementation of the Logger without affecting the UserService code.

6- Adapter pattern

The Adapter pattern is a structural design pattern that allows objects with incompatible interfaces to work together by wrapping the object in a class that adapts the interface of the original object to a new interface that is

Here’s an example of the Adapter pattern in Kotlin:

interface MediaPlayer {
fun play(fileType: String, fileName: String)
}

class MP3Player : MediaPlayer {
override fun play(fileType: String, fileName: String) {
if (fileType == "mp3") {
println("Playing MP3 file: $fileName")
} else {
println("Invalid file format")
}
}
}

class MediaAdapter(private val mediaType: String) : MediaPlayer {
private val vlcPlayer = VLCPlayer()
private val mp4Player = MP4Player()

override fun play(fileType: String, fileName: String) {
when (mediaType) {
"vlc" -> vlcPlayer.playVLC(fileName)
"mp4" -> mp4Player.playMP4(fileName)
else -> println("Invalid media format")
}
}
}

class VLCPlayer {
fun playVLC(fileName: String) {
println("Playing VLC file: $fileName")
}
}

class MP4Player {
fun playMP4(fileName: String) {
println("Playing MP4 file: $fileName")
}
}

class AudioPlayer : MediaPlayer {
private val mediaAdapter = MediaAdapter("")

override fun play(fileType: String, fileName: String) {
when (fileType) {
"mp3" -> MP3Player().play(fileType, fileName)
"vlc", "mp4" -> mediaAdapter.play(fileType, fileName)
else -> println("Invalid media format")
}
}
}

fun main() {
val audioPlayer = AudioPlayer()

audioPlayer.play("mp3", "music.mp3")
// Output: Playing MP3 file: music.mp3

audioPlayer.play("vlc", "video.vlc")
// Output: Playing VLC file: video.vlc

audioPlayer.play("mp4", "movie.mp4")
// Output: Playing MP4 file: movie.mp4

audioPlayer.play("avi", "file.avi")
// Output: Invalid media format
}

In this example, the AudioPlayer class uses the MediaAdapter class as a bridge between itself and the VLCPlayer and MP4Player classes. The AudioPlayer class does not support playing VLC or MP4 files directly, but by using the MediaAdapter class, it can play these file formats as well.

Common Design Patterns and App Architectures for Android

  1. Model-View-Controller (MVC) — This is one of the simplest and most popular design patterns for Android development. In MVC, the model represents the data and business logic of the application, the view displays the data, and the controller handles user input and updates the model and view accordingly.
  2. Model-View-Presenter (MVP) — This design pattern is similar to MVC, but it separates the view from the controller and replaces it with a presenter that acts as a bridge between the model and view. The presenter handles all the business logic and updates the view, while the view only communicates user interactions to the presenter.
  3. Model-View-ViewModel (MVVM) — This design pattern is similar to MVP, but it introduces the ViewModel, which acts as a provider of data for the view and abstracts the view from the model. The ViewModel is also responsible for performing any necessary transformations on the data from the model to make it ready for presentation in the view.
  4. Clean Architecture — This architecture is based on the principles of separation of concerns and dependency inversion. It divides the application into distinct layers, such as the data layer, domain layer, and presentation layer, and ensures that the dependencies between these layers are managed properly.
  5. Component-based Architecture — This architecture is based on the idea of breaking down the application into smaller, reusable components that can be combined to form a complete application. Each component is responsible for a specific part of the application, such as a feature or a UI element, and communicates with other components through well-defined interfaces.
  6. Event-Driven Architecture — This architecture is based on the idea of loosely coupling components and communicating between them using events. Each component listens for specific events and reacts to them by performing some action or sending out new events.

--

--