Common architectural design pattern for iOS and Android applications

Georgi Stanev
Prime Holding JSC
Published in
5 min readJul 5, 2019

As developers, we always aim to write concise, maintainable, and scalable code. When the project grows, new developers are joining the team, it becomes more and more difficult to stay close to the state of the art. In order to make our lives easier we strive to apply the best practices, but what happens if you need to work on the same project on both dominating mobile OS (Android & iOS)? What if in those you have a completely different code base, in terms of architectural design patterns, used programming paradigm, etc?

We at Prime Holding also faced the above-mentioned challenges so we had to come up with a streamlined and standardized way to do our jobs. And this is exactly what we did. We adopted an end-to-end solution to address these problems — a clearly defined set of rules to guide the flow of the application components. We based our solution on the MVVM architecture with the sole purpose of improving communication between components. Views pass data to view models only through input methods (as defined in an input interface), consume data only through output( utilized as observables defined in output interface), and receive the instance of the view model as a ViewModelType interface. View models tend to get bulky quickly, so this is solved by breaking down the view model into a few smaller view models and providing them through view model composition. And to make the load even lighter on our developers, we introduced templates to be integrated into the respective IDEs.

Firstly we had to choose among the most popular architectural design patterns for building native mobile applications, such as MVC, MVP, MVVM, MVI, and Viper. After weighing our options we decided that the most suitable pattern would be MVVM, as it provides enough separation of concerns, flexibility, and simplicity at the same time.

The Model-View-ViewModel (MVVM) pattern helps to cleanly separate the business and presentation logic of an application from its user interface (UI). Maintaining a clean separation between application logic and the UI helps to address numerous development issues and can make an application easier to test, maintain, and evolve. It can also greatly improve code re-use opportunities and allows developers and UI designers to more easily collaborate when developing their respective parts of an app

Secondly, we had to build our templates integrated right into the IDEs, so that we can create all architectural components with ease.

In order to make the concept clearer, let’s dive into all architectural layers, describing each of them in detail.

ViewController/View

A ViewController or a View, with their layout, represents the view layer. The role of the view in this pattern is to:

  • Define the structure, layout, and appearance of what the user sees on the screen
  • Observe (or subscribe to) ViewModel outputs (drivers/observables) to get data in order to update UI elements accordingly
  • Pass user actions to the ViewModel via its input methods.

ViewController example

ViewModel

The view model is an abstraction of the view exposing outputs (observables) and inputs (methods). Each ViewModel should encapsulate the business logic in a way that makes it easy to be reused across the entire app. The following rules have to be considered when a ViewModel is being implemented:

  • It should never reference a view.
  • It should encapsulate specific business logic.
  • The view model should generally not contain state, even though Stateful ViewModels are acceptable in case the models/collection should be locally manipulated Example: sorting/filtering/adding/removing items from a collection etc.
  • It should be unit testable.
  • For better readability, custom operators have to be implemented to serve specific Observable needs.

ViewModel example

Inputs

  • ViewModelInput is an interface that combines all inputs (methods) that the ViewModel should implement
  • For each input (method) a Publish/Behaviour Subject is declared so that all actions are transformed to Observables.

Outputs

  • ViewModelOutput is an interface that combines all outputs (Drivers) that the ViewModel should implement
  • Each output is represented as a Driver of presentable data (ViewState) that the consumer will use in order to update UI elements accordingly

ViewModelType

ViewModelType composes Input and Output. The ViewController is only aware of the ViewModelType as the ViewModel itself is hidden from it, so the only way to access the Inputs/Outputs is the following:

  • viewModel.input.*
  • viewModel.output.*

The above setup improves readability and allows implementing bindings for output streams.

Bindings

In order to glue View Layer (ViewController/View) with a ViewModel a ViewModelOutput/ViewModelInput extensions function should be implemented.

View Data Bindings

Where possible, the View Model’s Input/Output should be directly connected to the View Binding via extensions. RxCocoa is an extremely helpful tool for this purpose.

Read More

ViewModel Composition

In case of a complex screen with a variety of components, it should be split up into separate ViewModels wrapped in a single ViewModel via composition/DI (see below). Then that single view model should be instantiated in the ViewController with Dependency Injection (for example Swinject)

Service

The service layer is responsible for handling domain-specific business logic as it’s shared within multiple screens or even for the entire application. The goal is to offload the ViewModel of a shared business logic so that we keep ViewModel light and at the same time we maintain a good separation of concerns.

Repository

The repository pattern is a design pattern that isolates data access behind interface abstractions. Connecting to the database, Cache, or Web Service and manipulating data storage objects is performed through methods provided by the interface’s implementation. Each Repository contains methods that return Observables. The idea behind this is that when the ViewModel requests data, the Repository will retrieve it from any data source and will provide it via Observables since the data is expected to be fetched asynchronously and the response will not be delivered immediately.

Using those techniques, helped us significantly improve the code quality for our Android and iOS projects within the company, as the side effects are:

  1. It’s easier for developers to switch projects because of the common set of standards/patterns
  2. Having an Android developer transition to working on an iOS project is smooth and effortless since the learning curve is not steep at all
  3. Developers become more confident when facing different challenges since synchronization of events is easily done.
  4. Considering the impact of it on the company, I think that development times are reduced and the methodology carries very little technical debt.

Finally, you can check how a real case scenario would look in action by checking the samples below:

https://github.com/StanevPrime/PrimeHoldingAndroidTemplatesSample

https://github.com/StanevPrime/PrimeHoldingiOSTemplatesSample

--

--