Design Principles on Android

SOLID and more

Julián Falcionelli
6 min readApr 18, 2016

--

Knowing the principles of design is important to create a clean and modular design, allowing the extensibility and reusability of components in other projects, facilitating maintenance and avoiding re-work.

Below I will explain some of the best-known design principles in the object-oriented paradigm that I use, trying to make some reference to the Android SDK.

DRY (Don’t Repeat Yourself)

Do not write the same code more than once, whenever warranted. It is important to abstract repeated functionalities in one place, this makes the code more readable and helps a lot to maintain.

Something usual in Android applications is that often the activities share some behaviors, such as detecting when the device loses internet connection. A way to do this is by registering the activities to the CONNECTIVITY_CHANGE action. It would save a lot of work if we have a base activity registered to the corresponding receiver and then extend all the activities from this.

Also, you can use helper classes with static methods that are used in different parts of the application.

Another way to avoid repeating code is using the “static factory method” in the Activities and fragments, creating static methods that return the corresponding intent in the case that it is an activity or an instance of the fragment with all initializations.

Below you can find an example of this:

SOLID principles

The implementation of the SOLID principles attempts to have the code with:

  • high cohesion, ie each module performs a single task.
  • loose coupling, based on the degree of relationship between a module with others.

Thus the resulting project will be easier to design, program, test and maintain.

1) S (SRP: “Single Responsibility Principle”)

The single responsibility principle states that each class must have a unique responsibility. Otherwise, if a class has more than one function and a modification is introduced into any of these, it is possible that other functionality within the class affected.

To avoid this it’s important to have a good structure in the project, where you can categorize the scope of the classes. A basic structure could be:

It doesn’t necessarily have to be the final structure, it may change and grow.

If you want to know more about this, you can check out our Wiki’s larger structure with different responsibilities for each package.

It is important to be clear that the activities and fragments must be the bridge between business logic and visual components. This means that one must be responsible for the inflation views and event management that the user performs on them.

Remember that the fragments not only serve to change the contents of a view according to the size of the screen, they are very useful for encapsulating functionality and easing the activities' work.

2) O (OPC: “Principle of Open / Closed”)

“Software entity (class, module, function, etc.) should be open for extension, but closed for modification”.

That is, you should be able to extend the behavior of an entity without modifying the source code. This can be done through inheritance or using methods that receive interfaces so that the behavior of the class can change without having to modify them. It also prevents modifications to code that have already been tested.

3) L (LS: “Liskov Substitution”)

Image courtesy of Derick Bailey.

This principle is about the importance of creating appropriately derived classes so they can be treated as the parent class.

The subtypes must be replaceable for the supertype references without affecting the program execution.

If we want to manipulate derived objects as objects from the base class, we must ensure not to implement methods that stop the base class methods from working properly. As you can see, it is closely related to the OPC principle.

4) I (ISP: “Interface Segregation Principle”)

This principle is similar to the SRP principle but focused on interfaces. It defines that a class should not implement an interface if not used completely. To avoid this problem interfaces must have unique functionality.

5) D (DIP: “Dependency Inversion Principle”)

“Depend on abstractions, not depend on implementations”.

This phrase explains this principle perfectly. The aim is to decouple the classes. This brings the main benefit that if any component of the project changes, the replacement should not alter the other components that use it.

Something that happened recently was the surprise drop of Parse, so many systems must migrate to continue their services. When developing a project that feeds from a BaaS (Back End As Service), we use the principle of reversal of dependence, we abstract from it.

Here goes a simple example:

First, we define an interface with the methods required by the different modules that will use the service in the cloud:

Second, we define a concrete class that implements this interface:

Finally, to use the module instantiate an object of type BackEndAsService :

Thus if we no longer use Parse all you have to change in classes that use the interface BackEndAsService would be the sentence which instance the module, for example:

Dependency Injection

Dependency Injection is a design pattern that allows us to control inversion by providing an object to a class rather than the class itself that provides them.

There are many ways to do dependency injection, for example, a very common way is through the passage of objects (injection) in the class constructors.

A common way in Android is using de application class, where we can instantiate all modules that will be used in our activities.

For example:

There are several libraries that allow us to do dependency injection more easily, and we can say that today Dagger2 is the most popular.

Also, there are many libraries that help us to create maintainable code and uncoupled, some are:

  • Butterknife: Useful to inject views, saving us from writing findViewById() calls and reducing the amount of code necessary for handling events views,
  • EventBus: It facilitates communication between components. Useful to decouple the activities/fragments of business logic.

Using design patterns

Do not fear them, they are everywhere during the development of an Android application but you do not realize it.

Design patterns are seeking the simplest way to solve problems that often occur during development.

Some of the patterns in the development of an Android application are:

  • Singleton: It states that a class should be instantiated only once, as the Application class is instantiated once during the entire life cycle of our application.
  • Observer: It allows objects to be notified of any change of state of another object on which it depends. It is the behavior of the Broadcast receiver when activities subscribe to various events to perform different actions, such as detecting when our application loses internet.
  • Adapter: It allows two incompatible classes can work together. When creating a list of data, either to inflate in a ListView or RecyclerView the Adapters are used to send data from a controller to a particular view.
  • ViewHolder: Used in the Adapters to reuse views in the inflation process of the cells. This brings as a main benefit the proper management of memory and fluency in lists.
  • Builders: Used to create customized objects inline, stacking calls. Present during the creation of alerts (AlertDialog.Builder) and snackbars.

This was just a simple introduction, there are many interesting and useful principles of design.

Something I usually think when developing is that I wish that anyone who reads my code can understand easily, so using design patterns, and standards and being wordy facilitates communication and mainly avoid being hated by other developers.

--

--