Android tutorial

Common Design Patterns and App Architectures for Android

Common Design Patterns
806views

Common Design Patterns and App Architectures for Android. Learn how to improve your Android code more efficient and more easy to comprehend by following these standard design patterns that are used in Android applications. “Future Yourself” will be grateful!

Beyond your employer and clients There’s a third person to keep satisfied throughout your time as an engineer: Future You! The vision of the artist of Future You is no guarantee that the shirts you see on the rack will be offered for developers at some point in the in the near future.

Sometime down the path, Future You will inherit your code and will likely ask questions regarding why you wrote things in the manner you do. There are a lot of unclear comments within your code, but it is better to use the most common patterns of design and app Architectures.

If you are planning to quit your present job, you wish to make it easy for the person who will take the job. You do not want them to be praising your spaghetti codes that you left behind.

This article will explain the most popular design patterns and app Architectures you can utilize in the development of apps. Design Patterns can be used to provide solutions to the most common software issues. App Architectures can solve an application’s data flow or extensibility problems.

This isn’t an exhaustive listing of design Patterns or App Architectures or an academic study. This article is intended as a reference for hands-on use and as a reference point to further study.

Common Design Patterns In Android

Future: You must limit the time spent on research and scouring for complex dependencies in your project. Therefore, you must make your project as adaptable, user-friendly and easily identifiable as you can. These objectives range from an individual object to the whole project. They create patterns that fit into one of the categories below:

  • Patterns of creation How to make objects.
  • Patterns of structural structure What you can do to create objects.
  • Behavior patterns What you do to coordinate interaction between objects.

Design patterns typically address objects. They provide a solution to the problem that an object can solve and assist in eliminating particular design-related issues. Also they are challenges others have already encountered and stop you from creating a new problem by demonstrating proven methods to address those issues.

You can utilize one or more of these designs without needing “A Capitalized fancy name” to refer it. However, in the Future You will appreciate the fact that you didn’t just leave design decisions to your intuition.

In the following sections we’ll go over these themes from each category and how they work with Android:

Creational Patterns

  • Builder
  • Dependency Injection
  • Singleton
  • Factory

Structural Patterns

  • Adapter
  • Facade
  • Decorator
  • Composite

Behavioral Patterns

  • Command
  • Observer
  • Strategy
  • State

Creational Patterns Common Design Patterns  

“When I require an object that is particularly complicated and I need to get an exact copy?” – Future You

Future You is hoping that the answers aren’t ” Just copy and paste the same code each time you require an instance of that objects“. However, Creational patterns allow object creation to be simple and reproducible.

Builder

In a particular restaurant, you make your own sandwich. You select the bread, ingredients and condiments that you’d like to add to your sandwich using the checklist printed on a piece of paper. While the checklist will instruct you to make the perfect sandwich, just complete the form and then hand it to the counter. It’s not a sandwich you build but simply modify and eat it. :]

In the same way, similar to the pattern Builder pattern facilitates the construction of objects, such as cutting bread and stacking pickles in its representation, delicious sandwiches. So, the same method of construction can result in objects in the same category that have different characteristics.

In Android, one instance of the Builder pattern can be found in 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()

The builder is step-by-step and allows you to define only the components of AlertDialog that you have to define. Look over it’s AlertDialog.Builder documentation. There are many commands you can choose for your alert.

Dependency Injection

Dependency injection is similar to living in a fully furnished house. Everything you need is in the house. There’s no need be patiently waiting for delivery of furniture or the pages of IKEA instructions for assembling an Borgsjo bookshelf.

In the context of software it is required to supply any necessary objects to create an object. The new object does not need to be constructed or modified by the objects.

In Android You may find that you have to access the same complicated objects at different points within your app, for instance the network client, image loader, or SharedPreferences for local storage. It is possible to incorporate the objects you need into applications and even fragments of them and use immediately.

There are currently three main libraries used for dependency injection. They are Dagger 2, Dagger Hilt, and Koin. Let’s look at an example using Dagger. In Dagger, you mark the class using @Module and then add @Provides methods such as:

@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 configures and creates all the necessary objects. In addition, as a best practice for larger applications it is possible to create multiple modules that are separated by function.

You then create an component interface that lists your components and the classes you’ll add:

@Singleton
@Component(modules = [AppModule::class])
interface AppComponent {
    fun inject(activity: MainActivity)
   
}

The component links together the places where dependencies are emanating from as well as the modules and the places they’re headed to the points of injection.

In the end, you utilize then the @Inject annotation to request the dependency where you’ll need it, with the lateinit annotation to set up the property that is not nullable after you’ve created the object:

@Inject lateinit var sharedPreferences: SharedPreferences

In this case it is possible to use this within the Main Activity and then utilize local storage with the Activity having to be aware of exactly how SharedPreferences object was created.

This is admittedly only a brief overview, but you should read the Dagger’s documentation for more details about the implementation. It is also possible to follow the links provided in the libraries mentioned for detailed tutorials on each topic.

This design may appear complicated and amazing initially however, it could aid in simplifying your work and break them down into smaller pieces.

Singleton

It is a Singleton pattern states that only one instances of the class must be able to exist using the global access point. This pattern is useful when modeling real-world objects by having just one instance. For instance, if, for example, you own an object that has connection to databases or networks using multiple instances of your project could result in problems and mixing data. This is why, in certain scenarios it is recommended to limit to the development of multiple instances.

It is the Kotlin object keyword specifies a singleton and does not need to define an unchanging instance as is the case with other languages.

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

If you want to access individuals of the object singleton you can make calls similar to this:

ExampleSingleton.exampleMethod()

In the background in the background, behind the scenes, an instance static field is used to support an INSTANCE static field that supports the Kotlin object. If you want to access an Kotlin object made of Java codes, must modify the method as follows:

ExampleSingleton.INSTANCE.exampleMethod();

If you’re using objects it will be clear that you’re using the exact instance of this class in all of your apps.

The Singleton is most likely the easiest pattern to comprehend at first but it can be extremely easy to misuse and abuse. Because it’s accessible from a variety of objects, it can have unexpected consequences that are hard to find This is precisely the kind of thing that Future You doesn’t want to confront. It is important to know the design, however, other patterns could be more reliable and easy to keep in mind.

Factory

The factory is responsible for all object creation logic, as the name implies. A factory class is responsible for controlling which object is instantiated in this pattern. The factory pattern is useful when you need to deal with common objects. It can be used where you don’t want to specify a concrete type.

For a better understanding, take a look at this code:

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

    enum class HostingPackageType {
        STANDARD,
        PREMIUM
    }

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

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


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

Let’s take a look at the code.

  1. This interface is the basic one for all hosting plans.
  2. This enum lists all hosting packages.
  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.

It can be used in this way:

val standardPackage = HostingPackageFactory.getHostingFrom(HostingPackageType.STANDARD)

It allows you to keep all object creation within one class. A Factory class can become bloated if it is used in an inappropriate way. It can be difficult to test, as the factory class is responsible for all objects.

Structural Patterns Common Design Patterns 

“So when I open the class, how do I remember what it’s doing?” Future You

Future You will be able to appreciate the Structural patterns you used to organize your objects and classes into familiar structures that can perform common tasks. Facade and Adapter are two of the most common patterns found in Android.

Adapter

One memorable scene from the movie Apollo 13 shows a group of engineers who are given the task of fitting a square hole into a round peg. This is metaphorically the role of an adapter. This pattern allows two incompatible classes to work together using software. It converts a class’s interface into what the client expects.

Think about your app’s business logic. It could be a Product, a User, or Tribble. It’s the square hole. RecyclerView, on the other hand, is the same object in all Android apps. It is the round hole.

You can use a subclass RecyclerView in this case.

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 has never seen an episode of Star Trek or any new movies. It’s instead the job of the adapter to handle the data, and send the binding command the appropriate ViewHolder.

Facade

The Facade design provides an interface with a higher level that makes it easier to use other interfaces. This idea is illustrated in greater detail by the following diagram:

Your Activity should be able to request a single object from a list of books. It shouldn’t need to know the inside workings of your cache, local storage, and API client. Future You can make any necessary changes to the API implementation, without affecting the Activity’s Activities or Fragments code.

Square’s retrofit Android library is open-source and allows you to implement the Facade pattern. To provide API data to client class clients, you create an interface like this:

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

To receive a list Book items in the callback, the client must call tableBooks(). It’s simple and elegant. It could be that an army of Tribbles would be assembled and sent back via transporter beam. :]

You can make any customizations below without affecting clients. You can, for example, specify a custom JSON deserializer which the Activity does not know about.

val retrofit = Retrofit.Builder()
        .baseUrl("https://www.codeplayon.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. Retrofit allows you to further customize your operations using Interceptor or OkHttpClient, allowing you to control caching behavior and log behavior without the client being aware.

Future You will be able to manage the changes in the app easier if each object is less informed.

Decorator

To extend an object’s functionality, the Decorator pattern dynamically assigns additional responsibilities. Look at the following example:

    interface Salad {
        fun getIngredient(): String
    }

   
    class PlainSalad : Salad {
        override fun getIngredient(): String {
            return "Arugula & Lettuce"
        }
    }

    open class SaladDecorator(protected var salad: Salad) : Salad {
        override fun getIngredient(): String {
            return salad.getIngredient()
        }
    }

   
    class Cucumber(salad: Salad) : SaladDecorator(salad) {
        override fun getIngredient(): String {
            return salad.getIngredient() + ", Cucumber"
        }
    }

    
    class Carrot(salad: Salad) : SaladDecorator(salad) {
        override fun getIngredient(): String {
            return salad.getIngredient() + ", Carrot"
        }
    }

The following code defines the above:

  1. An Salad interface allows you to see the ingredients.
  2. A base is essential for every salad. This base is Lettuce so PlainSalad.
  3. An SaladDecorator allows you to add additional toppings to your PlainSalad.
  4. Cumcumber inherts SaladDecorator.
  5. Carrot inherts SaladDecorator.

You can easily extend your salad by using the SaladDecorator class. Any salad decorator can be removed or added to the code during runtime. Here are some examples of how to use it.

val cucumberSalad = Cucumber(Carrot(PlainSalad()))
print(cucumberSalad.getIngredient())
val carrotSalad = Carrot(PlainSalad())
print(carrotSalad.getIngredient())

Composite

The Composite Pattern is used when you need to create a tree-like structure made up of uniform objects. A composite pattern can contain two types of objects: a leaf and a composite. A composite object may have additional objects, while a leaf object is only the last.

To better understand the code, take a look at this:

interface Entity {
    fun getEntityName(): String
}

class Team(private val name: String) : Entity {
    override fun getEntityName(): String {
        return name
    }
}

class Raywenderlich(private val name: String) : Entity {
    private val teamList = arrayListOf<Entity>()

    override fun getEntityName(): String {
        return name + ", " + teamList.map { it.getEntityName() }.joinToString(", ")
    }

    fun addTeamMember(member: Entity) {
        teamList.add(member)
    }
}

The code below contains:

  1. Component is an interface Entity within the Composite pattern.
  2. An Team class implements Entities. It is a Leaf.
  3. Raywenderlich also implements a Entity interface. It is a composite.

Technically and logically, the organization (in this case Raywenderlich) adds an Entity the Team. Here are some ways to use it.

val composite = Raywenderlich("Ray")
    val ericTeamComposite = Raywenderlich("Eric")
    val aaqib = Team("Aaqib")
    val vijay = Team("Vijay")
ericTeamComposite.addTeamMember(aaqib)
            ericTeamComposite.addTeamMember(vijay)
            composite.addTeamMember(ericTeamComposite)
    print(composite.getEntityName())

Behavior Patterns Common Design Patterns 

“So…how do I tell which classes are responsible for what?” Future You

Behavioral patterns allow you to assign responsibility for different functions of your app. Future They can be used to help you navigate the project’s architecture and structure.

These patterns can be as broad as the relationships between objects or your entire app’s architecture. Developers often use multiple behavioral patterns in one app.

Command

You don’t know who will cook your Saag Paneer when you order it at an Indian restaurant. The waiter will take your order and post it in the kitchen for you.

Similar to the Command, you can issue requests without knowing who the receiver is. Send the request as an object by encapsulating it. It is a completely different process to decide how to fulfill the request.

Greenrobot’s Events is an Android framework that supports this design in the following way:

An Event can be triggered by user input or server data. You can also create subclasses that carry data.

class MySpecificEvent { /* Additional fields if needed */ }

After you have defined your event, you will be able to obtain an EventBus instance and register as a subscriber. You will have, for example, the following:

override fun onStart() {
  super.onStart()
  EventBus.getDefault().register(this)
}

override fun onStop() {
  super.onStop()
  EventBus.getDefault().unregister(this)
}

Once you have identified the object as a subscriber tell it what kind of event it would like to receive and what it should do if it does.

@Subscribe(threadMode = ThreadMode.MAIN)
fun onEvent(event: MySpecificEvent?) {
  /* Do something */
}

Create and publish an event based on your criteria.

EventBus.getDefault().post(MySpecificEvent())

Future You may have trouble following this pattern because so much of it works at run-time. If you don’t have enough test coverage, you might have some difficulty. However, a well-designed flow should balance out the difficulty and make it easy to follow.

Observer

The Observer design defines a one to many dependency between objects. If an object’s state changes, it notifies its dependents and updates them automatically.

This pattern can be used for many purposes. It can be used for indeterminate operations, such as API calls. It can also be used to respond to user input.

This pattern was first popularized by the RxAndroid framework. Also known as Reactive Android. This library allows you to implement this pattern in your app.

You define Observable objects which will emit value. These values can be emitted simultaneously, or in a continuous stream at any rate and for any duration.

Subscriber object will listen to these values and respond as soon as they arrive. You can, for example, open a subscription by making an API call. The server will respond and you can react accordingly.

Android recently introduced LiveData, a native method to implement this pattern. provides more information about this topic.

Strategy

Strategy is used when multiple objects have the same nature but different functionalities. The following code will help you understand the concept better:

Here is a breakdown of the code:

interface TransportTypeStrategy {
  fun travelMode(): String
}

class Car : TransportTypeStrategy {
  override fun travelMode(): String {
    return "Road"
  }
}

class Ship : TransportTypeStrategy {
  override fun travelMode(): String {
    return "Sea"
  }
}

class Aeroplane : TransportTypeStrategy {
  override fun travelMode(): String {
    return "Air"
  }
}

class TravellingClient(var strategy: TransportTypeStrategy) {
  fun update(strategy: TransportTypeStrategy) {
    this.strategy = strategy
  }

  fun howToTravel(): String {
    return "Travel by ${strategy.travelMode()}"
  }
}
  1. TransportTypeStrategy interface has a common type for other strategies so it can be interchanged at runtime.
  2. All the concrete classes conform to TransportTypeStrategy.
  3. TravellingClient creates strategy and uses it within the functions available to the client.

Here are some ways to use it.

val travelClient = TravellingClient(Aeroplane())
print(travelClient.howToTravel()) // Travel by Air
travelClient.update(Ship())
print(travelClient.howToTravel())

State

The State design pattern states that an object’s behavior changes when its internal state changes. The following snippets are available:

Here is a breakdown of the code:

interface PrinterState {
  fun print()
}

class Ready : PrinterState {
  override fun print() {
    print("Printed Successfully.")
  }
}

class NoInk : PrinterState {
  override fun print() {
    print("Printer doesn't have ink.")
  }
}

class Printer() {
  private val noInk = NoInk()
  private val ready = Ready()
  private var state: PrinterState
  private var ink = 2

  init {
    state = ready
  }

  private fun setPrinterState(state: PrinterState) {
    this.state = state
  }

  fun startPrinting() {
    ink--
    if (ink >= 0) {
      setPrinterState(ready)
    } else {
      setPrinterState(noInk)
    }
    state.print()
  }

  fun installInk() {
    ink = 2
    print("Ink installed.")
  }
}
  1. PrinterState describes the states of a printing device.
  2. Ready can be used to implement PrinterState and define a printer’s ready state.
  3. NoInk a concrete class that implements PrinterState, to indicate that the printer does not have ink.
  4. Printer Handler does all the printing.
  5. StartPrinting
  6. InstallInk Installs ink.

Here are some ways to use it.

val printing = Printer()
printing.startPrinting() // Printed Successfully.
printing.startPrinting() // Printed Successfully.
printing.startPrinting() // Printer doesn't have ink.
printing.installInk() // Ink installed.
printing.startPrinting()

You create an object of Printer that you want to print. All states are handled internally by the Printer class. It can be either in a Ready or in a Not Ink state.

App Architectures

“Could it ever be a requirement that I have to modify or create new features in this Project?” – Future You

App architectures are crucial in structuring loosely coupled codebases. It can be used anywhere, regardless of platform. App architectures allow you to write decoupled, testable and extensible code.

Architecture is simply the organization of your code. It can be described as:

  1. Each class has its own responsibility
  2. Folder organization
  3. Structure of the code: network calls and responses, errors.

Different types of app architectures

There are many App Architectures that can be used to build solid, maintainable codebases. This article will cover the most popular.

  • Model View Controller
  • Model View ViewModel
  • Clean Architecture

Model View Controller

Model view Controller or HTMLVC refers to one the most well-known architectural patterns, and one that many other architects have derived from. This is a very easy way to set up your Android project. Its name refers the three methods you can classify classes in your code.

    • Model: Your data classes. These models are useful if you have Product or User objects.

We use Data classes in Kotlin, but they are called POJOs (Plain Old Java Object) in Java.

  • View : Your visual classes. This category covers everything the user sees. Android’s view is XML. However, you can make views in code. Compose will allow you to have other types.
  • Controller is the glue between them. It updates the view, receives user input, and makes modifications to the model.

This will make your code decoupled and reusable.

Future You will eventually be asked by a client to add a screen to their app that utilizes its existing data. Future You can reuse the same models, but only modify the views by following the MVC paradigm.

Perhaps the client will ask Future You if you can move the widget from the home screen onto the detail screen. This is easy if you separate your view logic.

Your View layer will stay clean and tidy by moving as much layout logic and resource logic into Android XML. Nice!

Sometimes, you may need to draw in Kotlin. It’s possible to seperate the drawing operations from your activity or fragment classes in this case.

MVC and Future will make it easier to make architectural decisions. You’ll also be able to solve problems as they arise.

Model View ViewModel

This unfortunately-quite-confusingly-named presentation architectural pattern is similar to the MVC pattern. Both the Model and View components work in the same way.

The glue that links the model and view layers is the ViewModel object. However, it operates differently to the Controller component. It exposes commands to the view and binds it to the model. The data binding updates the views when the model is updated.

The bindings also work in reverse to update the model automatically when the user interacts directly with it.

Although the MVVM pattern is gaining popularity, it is still a relatively new addition to our pattern library. Google now recommends this pattern in its Architecture Components collection. Future This is one you should keep an eye on! :]

Clean Architecture

Clean Architecture does not constitute an architecture, but a concept. It is the overall architecture of an app. This includes how business objects, presenters, data storage, and UI interact with each other. Clean Architecture includes MVC and MVVM at the outer presentation layers and UI layers.

This article contains the original Clean Architecture definition. Many examples of Clean Architecture are available for Android, including Architecture Android…The evolution.

Read More Tutorial