Common Design Patterns and App Architectures for Android
Discover how to make your Android code cleaner and easier to understand with these common design patterns for Android apps. “Future You” will appreciate it! By Aaqib Hussain.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Common Design Patterns and App Architectures for Android
30 mins
- Getting Started
- Creational Patterns
- Builder
- Dependency Injection
- Singleton
- Factory
- Structural Patterns
- Adapter
- Facade
- Decorator
- Composite
- Behavioral Patterns
- Command
- Observer
- Strategy
- State
- App Architectures
- Types of App Architectures
- Model View Controller
- Model View ViewModel
- Clean Architecture
- Where to Go From Here?
Update Note: Aaqib Hussain updated this tutorial. Matt Luedke wrote the original.
Update Note: Aaqib Hussain updated this tutorial. Matt Luedke wrote the original.
Beyond your clients and employer, there’s one more important person you should keep happy in your career as a developer: Future You! The artist’s conception of Future You is no guarantee that such cool shirts will be available to developers in the near future. :]
At some point down the road, Future You will inherit the code you write and likely have several questions about why you coded things the way you did. You could leave tons of confusing comments in your code, but a better approach is to adopt common Design Patterns and App Architectures.
If you plan to leave your current job, you also want to make things easy for whoever takes over your role. You don’t want them praising the spaghetti code you left behind.
This article will introduce the most common Design Patterns and App Architectures you can use while developing apps. Design Patterns are reusable solutions to common software problems. App Architectures provide solutions to an app’s data flow or extensibility issues.
This isn’t an exhaustive list of Design Patterns and App Architectures or an academic paper. Instead, this article serves as a ‘hands-on reference’ and a starting point for further learning.
Getting Started
“Is there anywhere in this project where I’ll have to reuse the same piece of code?” – Future You
Future You should minimize time spent doing detective work, looking for intricate project dependencies. So you should create a project that’s as reusable, readable and recognizable as possible. These goals span from a single object all the way to the entire project and lead to patterns that fall into the following categories:
- Creational patterns: How you create objects.
- Structural patterns: How you compose objects.
- Behavioral patterns: How you coordinate object interactions.
Design patterns usually deal with objects. They present a solution to a reoccurring problem that an object shows and help eradicate design-specific problems. In other words, they represent challenges, other developers already faced and prevent you from reinventing the wheel by showing you proven ways to solve those problems.
You may use one or several of these patterns already without having “A Capitalized Fancy Name” for it. However, Future You will appreciate that you didn’t leave design decisions to intuition alone.
In the sections that follow, you’ll cover these patterns from each category and see how they apply to Android:
Creational Patterns
- Builder
- Dependency Injection
- Singleton
- Factory
Structural Patterns
- Adapter
- Facade
- Decorator
- Composite
Behavioral Patterns
- Command
- Observer
- Strategy
- State
Creational Patterns
“When I need a particularly complex object, how do I get an instance of it?” – Future You
Future You hopes the answer isn’t “Just copy and paste the same code every time you need an instance of this object“. Instead, Creational patterns make object instantiation straightforward and repeatable.
Builder
At a certain restaurant, you create your own sandwich: you choose the bread, ingredients and condiments you’d like on your sandwich from a checklist on a slip of paper. Even though the checklist instructs you to build your own sandwich, you only fill out the form and hand it over the counter. You don’t build the sandwich, just customize and consume it. :]
Similarly, the Builder pattern simplifies the creation of objects, like slicing bread and stacking pickles, from its representation, a yummy sandwich. Thus, the same construction process can create objects of the same class with different properties.
In Android, an example of the Builder pattern is AlertDialog.Builder
:
AlertDialog.Builder(this)
.setTitle("Sandwich Dialog")
.setMessage("Please use the spicy mustard.")
.setNegativeButton("No thanks") { dialogInterface, i ->
// "No thanks" action
}
.setPositiveButton("OK") { dialogInterface, i ->
// "OK" action
}
.show()
This builder proceeds step-by-step and lets you specify only the parts of AlertDialog
that you need to specify. Take a look at the AlertDialog.Builder
documentation. You’ll see there are quite a few commands to choose from when building your alert.
The code block above produces the following alert:
A different set of choices would result in a completely different sandwich– er, alert. :]
Dependency Injection
Dependency injection is like moving into a furnished apartment. Everything you need is already there. You don’t have to wait for furniture delivery or follow pages of IKEA instructions to put together a Borgsjö bookshelf.
In software terms, dependency injection has you provide any required objects to instantiate a new object. This new object doesn’t need to construct or customize the objects themselves.
In Android, you might find you need to access the same complex objects from various points in your app, such as a network client, image loader or SharedPreferences
for local storage. You can inject these objects into your activities and fragments and access them right away.
Currently, there are three main libraries for dependency injection: Dagger ‘2’, Dagger Hilt, and Koin. Let’s take a look at an example with Dagger. In it you annotate a class with @Module
, and populate it with @Provides
methods like:
@Module
class AppModule(private val app: Application) {
@Provides
@Singleton
fun provideApplication(): Application = app
@Provides
@Singleton
fun provideSharedPreferences(app: Application): SharedPreferences {
return app.getSharedPreferences("prefs", Context.MODE_PRIVATE)
}
}
The module above creates and configures all required objects. As an additional best practice in larger apps, you could create multiple modules separated by function.
Then, you make a Component
interface to list your modules and the classes you’ll inject:
@Singleton
@Component(modules = [AppModule::class])
interface AppComponent {
fun inject(activity: MainActivity)
// ...
}
The component ties together where the dependencies are coming from, the modules, and where they’re going to, the injection points.
Finally, you use the @Inject
annotation to request the dependency wherever you need it, along with lateinit
to initialize a non-nullable property after you create the containing object:
@Inject lateinit var sharedPreferences: SharedPreferences
As an example, you could use this in your MainActivity
and then use local storage, without the Activity needing to know how the SharedPreferences
object came to be.
Admittedly, this is a simplified overview, but you can read the Dagger documentation for more implementation details. You can also click the links above in the mentioned libraries for in-depth tutorials for each topic.
This pattern may seem complicated and magical at first, but it can help simplify your activities and fragments.