ChairNerd

Code, Design, and Growth at SeatGeek

Jobs at SeatGeek

We currently have more than 10 open positions.

Visit our Jobs page

Software Design Patterns in Android Development

In just the past few years, mobile development has changed greatly in terms of not only what is capable on a pocket-sized device, but also in terms of what users want and expect out of the average app. With that, we developers have had to change how we build things in order to account for more complex, robust applications. The Android development community in particular has had a huge shift in focus (hopefully 🙏) from massive, logic-heavy Activities, to MVWhatever based approaches to help split our logic out. Most of us have also adopted the Observer Pattern through libraries like RxJava, dependency injection with Dagger, and more extensive testing. These tools, along with the adoption of Kotlin and the introduction of Architecture Components, have made it easier and easier to get off on the right foot with new Android apps to help ensure their maintainability and scalability for the future. However, these simple patterns are really just the tip of the iceberg and as our apps have some time to grow and mature in the wild, it is often necessary for us to employ other design patterns to allow applications to grow without becoming an unmaintainable mess. In this article, I’d like to take a look at some other common object oriented design patterns that have been around for while in the software world, but are talked about a little less in the typical Android development article. Before taking off and trying to implement these on your app as soon as you’re done reading, it’s worth noting that although these are good principles to abide by, using all or even some of them when they aren’t really necessary can lead to overly convoluted code that ends up being harder to understand than it needs to be. Not every app needs to implement them, but the key is to know when it’s time to start. Okay, enough of an intro, let’s get started!

Note: All examples are in Kotlin, so it’s recommended to be fairly familiar with the language and / or have this reference guide on hand

The Adapter Pattern

The adapter pattern is a strategy for adapting an interface from one system so that it works well in another, usually by abstracting away implementation via an interface. It is a common and useful design pattern that can help make components that are hard to reuse or integrate into a system more susceptible to doing so by changing (wrapping) their interface with one we design ourselves. In fact, it’s no coincidence that a RecyclerView.Adapter is named as such. Although every RecyclerView (or even each cell in a single RecyclerView) is going to likely display something different, we can utilize the common adapter interface for each ViewHolder. This allows for an easy way to hook into a RecyclerView but also gives the flexibility of allowing for different data types to be shown. This idea can be utilized in other areas as well. For example, say we’re working with a web API that doesn’t quite give us the data we need throughout the rest of our app. Maybe it gives us too much info, or gives shortened versions / acronyms of certain information in order to save cellular data, etc. We can build our own adapters to convert these API results to something that useful to the rest of our application. For example let’s take a look assume we’re making a basic ticketing app and have a TicketResponse that looks something like this after being converted from JSON to a Kotlin data class (note: not actually what our web API looks like):

1
2
3
4
5
6
data class TicketResponse(
    val seat: String = "R17S6",
    val price: Double = 50.55,
    val currency: String = "USD",
    val type: String = "Mobile"
)

For the sake of example, ignore the debate on whether or not this is a good looking API model. Let’s assume that it is, but regardless it’s a little messy for us to use throughout our application. Instead of R17S6 we’d really like to have a Row model with a value of 17, and a Seat model with a value of 6. We’d also like to have some sort of Currency sealed class with a USD child that will make life much easier than what the API currently gives us. This is where our the Adapter pattern comes in handy. Instead of having our (presumably Retrofit based) API return the TicketResponse model, we can instead have it return a nicely cleaned up Ticket model that has everything we want. Take a look below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
data class Ticket(
    val row: Row,
    val seat: Seat,
    val price: Currency,
    val type: TicketType
)

class TicketApiClient {
    fun getTicket(): Ticket {
        val ticketResponse : TicketResponse = getTicketResponse()
        return TicketAdapter.adapt(ticketResponse)
    }
}

object TicketAdapter {
     fun adapt(ticketResponse: TicketResponse) = Ticket(
        row = TicketUtil.parseRow(ticketResponse.seat),
        seat = TicketUtil.parseSeat(ticketResponse.seat),
        price = CurrencyUtil.parse(ticketResponse.price),
        type = TicketUtil.parseType(ticketResponse.type)
    )
}

Although there’s still some improvements that could be made here (Api likely would return an RxJava Single for threading purposes), this little adapter gives us a nice clean Ticket model that’s much easier to use throughout the rest of our app than the model our Api gives us. There’s obviously many more usages for the adapter pattern, but this is a pretty straightforward one that most apps will likely find useful at some point along the way.

The Façade Pattern

Simply put, the façade pattern hides complex code behind a simple, easy to use interface. As our apps grow, there is bound to be some complicated business logic that comes about in order to implement product requirements. The façade pattern helps us not to have to worry about how those complex business logic implementations work, and instead let us plug into them very easily. Staying on the ticket theme, we may have some caching mechanisms implemented to ensure that users are able to view their tickets, even though they may not have service in the venue when they scan in. This logic is probably housed in some sort of TicketStore class that may have references to our TicketApiClient class above, our database, and maybe even a memory cache so we can load tickets as quick as possible. Determining where to load the tickets from can get pretty complicated pretty quickly and we therefore want to keep this logic isolated and abstracted away from those classes simply trying to get a reference to a ticket object to display on the screen for example. Let’s say we have a basic Model-View-Presenter architecture set-up. Our presenter shouldn’t have to worry about whether the ticket comes from the server, a memory cache, or from disk, it should just be able to say “Hey TicketStore, give me a ticket!” and get one back without having any knowledge of where it came from. This is where the Façade pattern comes into play. Our TicketStore class is a Façade that hides anything related to storing tickets behind a simple interface with potentially one simple method called something like getTicket(id: Int) : Ticket. Integrating with this interface now becomes incredibly easy.

1
2
3
4
5
6
7
class TicketPresenter(view: TicketView, ticketStore: TicketStore) {
    init {
        ticketStore.getTicket(SOME_ID)?.let {
            view.show(it)
        }
    }
}

The Command Pattern

The command pattern utilizes objects (value / data classes) to house all the information needed to perform some action. This is a design pattern that is becoming more and more common in Android development these days, largely due to lambdas / higher-order functions (functions that take in functions) available in Java 8 / Kotlin. With the command pattern, we can encapsulate a request or an action as an object. This object can take in function types that are executed when the request is handled. We tend to typically use these with third party libraries such as RxJava, but they can actually be useful for various internal APIs that we may have throughout our applications as well. Consider some sort of UI transition API we’ve created internally to help manage different view states. Let’s say we want to transition from the Loading state of our screen to the Content state. Let’s also say want certain actions to be executed before and after we make this transition. With the command pattern (and Kotlin), this becomes fairly trivial.

First we have our Transition class that takes in the layout to transition to, as well as functions to be executed during the transition process.

1
2
3
4
5
6
data class Transition(
    @LayoutRes val layoutRes: Int,
    val beforeTransition: () -> Unit,
    val onViewInflated: (View) -> Unit,
    val afterTransition: () -> Unit
)

We then have some sort of TransitionCoordinator class that is able to take in Transition objects and perform our transition as well as execute these various actions when needed. The key piece of the puzzle that makes the command pattern so useful is that the TransitionCoordinator doesn’t need really know anything about the calling class in order to go about this transition process. It is very generic and can take in functions from any class without knowing its innards. This makes the command pattern very powerful while being quite generic. This TransitionCoordinator might look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class TransitionCoordinator {
    fun startTransition(transition: Transition) {
        transition.run {
            beforeTransition()
            val view = inflate(layoutRes)
            onViewInflated(view)
            doTransition()
            afterTransition()
        }
    }

    fun inflate(@LayoutRes layoutRes: Int): View = { /* Inflation code */ }

}

Thanks to the brevity of Kotlin, we are able to execute this transition process in very few lines of code.

In our Activity (or wherever we’re calling this code) we can now do something like this.

1
2
3
4
5
6
7
8
transitionCoordinator.startTransition(
    Transition(
        layoutRes = R.layout.activity_sample,
        beforeTransition = { unsubscribeObservables() },
        onViewInflated = { view -> bind(view) },
        afterTransition = { fetchData() }
    )
)

In this scenario we want to transition to some sample activity, unsubscribe to some observables before our transition, bind the newly inflated view upon transition, and fetch data after we’ve transition. Of course, once we brought this into the real world, our implementation would likely not be this simple, but it’s easy to see that encapsulating these method calls with “Command” objects makes them generic and easy to use.

Null Object Pattern

One other convenient design pattern, but possibly less frequently used is the “Null Object Pattern”. For those of us who have been Android developers for ~2+ years (before Kotlin came into play), we are all too familiar with null pointer exceptions that bring our apps to a crippling crash. Kotlin has helped to reduce this greatly, but there are still some cases in which we may need to account for null or empty states. The null object pattern can help us in scenarios where, although we may not have the necessary data to say populate a view, we still want to show some sort of empty (null) state in the meantime. This is only one potential example, but is a good place to start. Consider some sort of MVWhatever framework we’ve implemented in our UI layer. Our View takes in ViewModels and shows them on the screen. Let’s say for instance we’re displaying a user’s profile. In the ideal scenario, we simply fetch the profile from our database, cache, api or wherever and display it on the screen. Simple as that. However, what do we show if api request is taking a very long time? Maybe the user has bad cell signal or our server is at high demand for some reason. Of course we’ll want to show some sort of loading indicator here to let the user know we’re working on loading this info, but one other cool thing we can do is actually populate the view with an “null” or empty user state. Many popular apps (Facebook, Twitter, etc) utilize this type of pattern to give the app the appearance of loading even faster than it technically is. Let’s take a look at brief bit of code to help make better sense of this.

1
2
3
interface ViewModel {
     val titleText: String
}
1
data class UserViewModel(val user: User, override val titleText: String = user.name) : ViewModel
1
data class NullViewModel(override val titleText: String = "") : ViewModel

So what we have here is a ViewModel interface that holds the titleText we want to show on the screen as well as a couple of data classes that implement this interface and pass the necessary bits of data for the view to show what it needs to. Take a look at the NullViewModel data class. It might look pointless to pass this object to the view since there’s really nothing to show. However, by passing this model instead of waiting, our view can start to draw and allocate space on the screen. We can then even recycle this view when a real UserViewModel is available to make our rendering time even faster. Again, this is just one of many applications of this design pattern, and although it’s very simple it can be very useful in the right scenario.


Hope you enjoyed this little list of some design patterns less talked about in the Android development community. Again, there are not always applications for all of these, and they should only be used when the provide a tangible benefit to your app. There’s many more design patterns out there, each with their own specific purpose. A couple great books that go more in depth into different patterns can be found here and here. Happy coding!


We’re hiring! Learn more here

Comments