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.

4.4 (10) · 1 Review

Save for later
Share

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.

Note: This article assumes you’re familiar with the basics of Android development. If you’re new to Kotlin or the Android platform, take a look at Beginning Android Development Series and Kotlin For Android: An Introduction before you start.

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
Note: This article isn’t like a traditional raywenderlich.com tutorial because it doesn’t have an accompanying sample project you can follow along with. Instead, treat it as an article to get you up to speed with the different patterns you’ll see in our other Android tutorials and a way to improve your code.

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:

Android Message Alert Dialog

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.

Android injecting dependencies image