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
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Singleton

The Singleton pattern specifies that only a single instance of a class should exist with a global access point. 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 like in other languages:

object ExampleSingleton {
  fun exampleMethod() {
    // ...
  }
}

When you need to access members of the singleton object, you make a call like this:

ExampleSingleton.exampleMethod()

Behind the scenes, an INSTANCE static field backs the Kotlin object. So, if you need to use a Kotlin object from Java code, you modify the call like this:

ExampleSingleton.INSTANCE.exampleMethod();

By using object, you’ll know you’re using the same instance of that class throughout your app.

The Singleton is probably the most straightforward pattern to understand initially but can be dangerously easy to overuse and abuse. Since it’s accessible from multiple objects, the singleton can undergo unexpected side effects that are difficult to track down, which is exactly what Future You doesn’t want to deal with. It’s important to understand the pattern, but other design patterns may be safer and easier to maintain.

Factory

As the name suggests, Factory takes care of all the object creational logic. In this pattern, a factory class controls which object to instantiate. Factory pattern comes in handy when dealing with many common objects. You can use it where you might not want to specify a concrete class.

Take a look at the code below for a better understanding:

// 1
interface HostingPackageInterface {
  fun getServices(): List<String>
}

// 2
enum class HostingPackageType {
  STANDARD,
  PREMIUM
}

// 3
class StandardHostingPackage : HostingPackageInterface {
  override fun getServices(): List<String> {
    return ...
  }
}

// 4
class PremiumHostingPackage : HostingPackageInterface {
  override fun getServices(): List<String> {
    return ...
  }
}

// 5
object HostingPackageFactory {
  // 6
  fun getHostingFrom(type: HostingPackageType): HostingPackageInterface {
    return when (type) {
      HostingPackageType.STANDARD -> {
        StandardHostingPackage()
      }
      HostingPackageType.PREMIUM -> {
        PremiumHostingPackage()
      }
    }
  }
}

Here’s a walk through the code:

  1. This is a basic interface for all the hosting plans.
  2. This enum specifies all the hosting package types.
  3. StandardHostingPackage conforms to the interface and implements the required method to list all the services.
  4. PremiumHostingPackage conforms to the interface and implements the required method to list all the services.
  5. HostingPackageFactory is a singleton class with a helper method.
  6. getHostingFrom inside HostingPackageFactory is responsible for creating all the objects.

You can use it like this:

val standardPackage = HostingPackageFactory.getHostingFrom(HostingPackageType.STANDARD)

It helps to keep all object creation in one class. If used inappropriately, a Factory class can get bloated due to excessive objects. Testing can also become difficult as the factory class itself is responsible for all the objects.

Structural Patterns

“So, when I open this class, how will I remember what’s it doing and how it’s put together?” – Future You

Future You will undoubtedly appreciate the Structural Patterns you used to organize the guts of your classes and objects into familiar arrangements that perform typical tasks. Adapter and Facade are two commonly-seen patterns in Android.

Adapter

A famous scene in the movie Apollo 13 features a team of engineers tasked with fitting a square peg into a round hole. This, metaphorically, is the role of an adapter. In software terms, this pattern lets two incompatible classes work together by converting a class’s interface into the interface the client expects.

Consider your app’s business logic. It might be a Product or a User or Tribble. It’s the square peg. Meanwhile, a RecyclerView is the same basic object across all Android apps. It’s the round hole.

In this situation, you can use a subclass of RecyclerView.Adapter and implement the required methods to make everything work:

class TribbleAdapter(private val tribbles: List<Tribble>) : RecyclerView.Adapter<TribbleViewHolder>() {
  override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): TribbleViewHolder {
    val inflater = LayoutInflater.from(viewGroup.context)
    val view = inflater.inflate(R.layout.row_tribble, viewGroup, false)
    return TribbleViewHolder(view)
  }

  override fun onBindViewHolder(viewHolder: TribbleViewHolder, itemIndex: Int) {
    viewHolder.bind(tribbles[itemIndex])
  }

  override fun getItemCount() = tribbles.size
}

RecyclerView doesn’t know what a Tribble is, as it’s never seen a single episode of Star Trek, not even the new movies. :] Instead, it’s the adapter’s job to handle the data and send the bind command to the correct ViewHolder.

Facade

The Facade pattern provides a higher-level interface that makes a set of other interfaces easier to use. The following diagram illustrates this idea in more detail:

block diagram of a facade block pointing to three boxes below it with names: Dependency A, Dependency B, and Dependency C

If your Activity needs a list of books, it should be able to ask a single object for that list without understanding the inner workings of your local storage, cache and API client. Beyond keeping your Activities and Fragments code clean and concise, this lets Future You make any required changes to the API implementation without impacting the Activity.

Square’s Retrofit is an open-source Android library that helps you implement the Facade pattern. You create an interface to provide API data to client classes like so:

interface BooksApi {
  @GET("books")
  fun listBooks(): Call<List<Book>>
}

The client needs to call listBooks() to receive a list of Book objects in the callback. It’s nice and clean. For all it knows, you could have an army of Tribbles assembling the list and sending it back via transporter beam. :]

This lets you make all types of customizations underneath without affecting the client. For example, you can specify a customized JSON deserializer that the Activity has no clue about:

val retrofit = Retrofit.Builder()
  .baseUrl("http://www.myexampleurl.com")
  .addConverterFactory(GsonConverterFactory.create())
  .build()

val api = retrofit.create<BooksApi>(BooksApi::class.java)

Notice the use of GsonConverterFactory, working behind the scenes as a JSON deserializer. With Retrofit, you can further customize operations with Interceptor and OkHttpClient to control caching and logging behavior without the client knowing what’s going on.

The less each object knows about what’s going on behind the scenes, the easier it’ll be for Future You to manage changes in the app.

Image of Android with gears inside its stomach