S.O.L.I.D Principle in Android

Deepak Sikka
7 min readDec 10, 2022

We must all have heard about the S.O.L.I.D principle when developing software tools.

So let’s begin by stating the 5 principles of S.O.L.I.D.

S — The Single Responsibility Principle (SRP):A class should have only one reason to change.

What I understood :This means that one class should only have one responsibility.

Scenario: Let’s take the OnBindViewHolder method in RecyclerView.Adapter class. The role of the OnBindViewHolder is to map an list item to a view. There should be no logic in this method.

Let’s look at the below example to see how the SRP principle is violated in the OnBindViewHolder method in this scenario:

@Override 
public void onBindViewHolder(ViewHolder holder, int position) {
Movie movie = movies.get(position);

holder.title.setText(movie.getTitle());
holder.rating.setText(movie.getRating());

//SRP violation, onBindViewHolder has only the responsibility to display data
// & not make data formatting operations

String[] genres = song.getGenres();
StringBuilder builder = new StringBuilder();
foreach (String genre : genres){
builder.append(genre).append(",");
}

holder.genres.setText(builder.toString());
}

OnBindViewHolder has only one responsibility which is to display data & not make data formatting operations.

Now, let’s look at the correct way to implement the OnBindViewHolder method using SRP principle:

@Override 
public void onBindViewHolder(ViewHolder holder, int position) {
Movie movie = movies.get(position);

holder.title.setText(movie.getTitle());
holder.rating.setText(movie.getRating())

//all the logic is moved into util class...now is clean!
holder.authors.setText(AppUtils.getGenres(movie))
}

All the logic is moved into the Util class. So now the code is clean!

O — The Open-Closed Principle (OCP): Software entities such as classes, functions, modules should be open for extension but not modification.

What I understood: This means that if we are required to add a new feature to the project, it is good practice to not modify the existing code but rather write new code that will be used by the existing code.

Scenario: Let’s say we have a class called TimeOfDayGreeting with a single method getGreetingFromTimeOfDay. We would like to display a greeting message when the user opens the app. This message must be based on the time of the day.

Let’s look at the below example to see how the OCP principle is violated in this scenario:

public class TimeOfDayGreeting {
private String timeOfDay;

/*
* Every time this method is called it will
* called an if else logic, which is in violation of the
* OCP principle.
*/
public String getGreetingFromTimeOfDay() {
if (this.timeOfDay == "Morning") {
return "Good Morning, sir.";
}
else if (this.formality == "Afternoon") {
return "Good Afternoon, sir.";
}
else if (this.formality == "Evening") {
return "Good Evening, sir.";
}
else {
return "Good Night, sir.";
}
}

public void setTimeOfDay(String timeOfDay) {
this.timeOfDay = timeOfDay;
}
}

Every time this method is called, if else condition will be executed, which violates the OCP principle

Now, let’s look at the correct way to implement this feature using the OCP principle:

/* Create an interface called TimeOfDay and let the Morning, Afternoon, 
* Evening classes implement this interface.
* This interface can then be called inside the TimeOfDayGreeting class.
* This means the getGreetingFromTimeOfDay() method need not handle
* any logic
*/
public class TimeOfDayGreeting {
private TimeOfDay timeOfDay;

public TimeOfDayGreeting(TimeOfDay timeOfDay) {
this.timeOfDay = timeOfDay;
}

public String getGreetingFromTimeOfDay() {
return this.timeOfDay.greet();
}
}


public interface TimeOfDay {
public String greet();
}

/* Morning class */
public class Morning implements TimeOfDay {
public String greet() {
return "Good morning, sir.";
}
}

/* Afternoon class */
public class Afternoon implements TimeOfDay {
public String greet() {
return "Good afternoon, sir.";
}
}

/* Evening class */
public class Evening implements TimeOfDay {
public String greet() {
return "Good evening, sir.";
}
}

/* Night class */
public class Night implements TimeOfDay {
public String greet() {
return "Good night, sir.";
}
}

Created an interface to handle action. Created 4 different classes for 4 different times of the day. The TimeOfDayGreeting class does not handle any logic now.

L — The Liskov Substitution Principle (LSP): Child classes should never break the parent class’ type definitions.

What I understood: This means that a sub class should override the methods from a parent class that does not break the functionality of the parent class.

Scenario: Let’s say we have a interface ClickListener. This interface is implemented by the fragments 1 & 2. We would need to implement the ClickListener interface in both the fragments. Our requirement is to increment click count in fragment 2 but decrement click count in fragment 1.

Let’s look at the below example to see how the LSP principle is violated in this scenario:

public interface ClickListener {
public void onClick();
}

public class Fragment1 implements ClickListener {
@Override
public void onClick() {
//handle logic
}

public void decrementClickCount() {

}
}

public class Fragment2 implements ClickListener {
@Override
public void onClick() {
//handle logic
}

public void incrementClickCount() {

}
}



public void onButtonClick(ClickListener clickListener) {
// IF we have a requirement where we need to increment the click count in
// framgent2 but decrement the count in fragment 1
// we would have to follow something like this, which is bad practice.
if(clickListener instanceOf Fragment2) {
clickListener.incrementClickCount();

} else if(clickListener instanceOf Fragment1) {
clickListener.decrementClickCount();
}

clickListener.onClick();
}

Now, let’s look at the correct way to implement this feature using the LSP principle:

public interface ClickListener {
public void onClick();
}

public class Fragment1 implements ClickListener {
@Override
public void onClick() {
decrementClickCount();
//handle logic
}

public void decrementClickCount() {

}
}

public class Fragment2 implements ClickListener {
@Override
public void onClick() {
incrementClickCount();
//handle logic
}

public void incrementClickCount() {

}
}

/*
* We handle the individual logic inside the overridden methods
* in the framgents. In the main implementation we should
* never handle logic
*/
public void onButtonClick(ClickListener clickListener) {
clickListener.onClick();
}

I— The Interface Segregation Principle (ISP):The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use.

What I understood: This means that if an interface becomes too fat, then it should be split into smaller interfaces so that the client implementing the interface does not implement methods that are of no use to it.

Scenario: Let’s take the TextWatcher interface in Android. We know that the TextWatcher interface has 3 methods. This is an example of how this principle is violated, since we only use one of the methods most of the time.

/* 
* There are 3 methods to the TextWatcher interface.
* But in most scenarios we need to use only one method.
* This means ideally the remaining two methods have no use and should
* not be part of the interface. This is in violation of the ISP principle
*/
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {

}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
/* In most of the scenarios this is the only method we use. The other methods are pointless in these cases. */
}

@Override
public void afterTextChanged(Editable editable) {

}
});

Now, let’s look at the correct way to implement this feature using the ISP principle:

/* We create an interface with one method  */

public interface TextWatcherWithInstance {
void onTextChanged(EditText editText, CharSequence s, int start, int before, int count);
}


/* We create a custom class called MultiTextWatcher.
* And pass the interface here
*/
public class MultiTextWatcher {
private TextWatcherWithInstance callback;

public MultiTextWatcher setCallback(TextWatcherWithInstance callback) {
this.callback = callback;
return this;
}

public MultiTextWatcher registerEditText(final EditText editText) {
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
callback.onTextChanged(editText, s, start, before, count);
}

@Override
public void afterTextChanged(Editable editable) {}
});

return this;
}


/*
* We can call this class from our Activity/Fragment like this:
* This only has one method, which we are using in the app
*/
new MultiTextWatcher()
.registerEditText(editText)
.setCallback(new MultiTextWatcher.TextWatcherWithInstance() {
@Override
public void onTextChanged(EditText editText, CharSequence s, int start, int before, int count) {

}
});

D — The Dependency Inversion Principle (DIP):High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend upon details. Details should depend upon abstractions.

What I understood: If you use a class insider another class, this class will be dependent of the class injected.

Scenario: Let’s say we have a class called JobTracker. The requirement is to update users via email or call based on the urgency of the job.

Let’s look at the below example to see how the DIP principle is violated in this scenario:

/*  
* Class called Emailer to send email alerts
*/
public class Emailer {
public String generateJobAlert(String job) {
String alert = "You are alerted for " + job;
return alert;
}
}

/*
* Class called Phone to send phone alerts
*/
public class Phone {
public String generateJobAlert(String job) {
String alert = "You are alerted for " + job;
return alert;
}
}


/*
* Class called JobTracker.
* This class initializes both the Phone & Email class
* This is a violation of the DIP principle
*/
public class JobTracker {
private Phone phone;
private Emailer emailer;

public JobTracker() {
phone = new Phone();
emailer = new Emailer();
}

/*
* Based on the jobDescription, the alert is sent
* This logic should not be implemented here!
*/
public void setCurrentConditions(String jobDescription) {
if (jobDescription == "urgent") {
String alert = phone.generateJobAlert(jobDescription);
System.out.print(alert);
}
if (jobDescription == "brief") {
String alert = emailer.generateJobAlert(jobDescription);
System.out.print(alert);
}
}
}

Now, let’s look at the correct way to implement this feature using the DIP principle:

/*  
* Solution step 1: Create an interface called Notifier.
* It has one method: jobAlert(String jobDescription);
*/
public interface Notifier {
public void jobAlert(String jobDescription);
}


/*
* Step II:
* Create an emailClient class that implements
* Notifier interface
*/
public class EmailClient implements Notifier {
public void jobAlert(String jobDescription) {
if (jobDescription == "brief");
System.out.print("Job description is brief");
}
}

/*
* Step III:
* Create an PhoneClient class that implements
* Notifier interface
*/
public class PhoneClient implements Notifier {
public void jobAlert(String jobDescription) {
if (jobDescription == "urgent");
System.out.print("Job description is urgent");
}
}


/*
* Step IV: The JobTracker class would just
* call the respective notifiers. It does not
* handle any logic
*/
public class JobTracker {
private String currentAlert;

public void setCurrentConditions(String jobDescription) {
this.currentAlert = jobDescription;
}

public void notify(Notifier notifier) {
notifier.jobAlert(currentAlert);
}
}

And that’s it! If you like this and think I have done a good job here then please don’t forget to clap. Your appreciation is valuable and keeps me motivated.

Happy Coding, Cheers!!

--

--

Deepak Sikka

Senior Android Developer. Working on technology Like Java,Kotlin, JavaScript.Exploring Block Chain technology in simple words.