Are you looking for a better architecture that fits your complex Android apps? Have you heard about Clean Architecture but don’t know if it will fit your Android app?
In my talk, you will learn how to Adapting clean architecture in Android apps including:
What is Clean Architecture?
Layers of a Clean Architecture?
What are SOLID design principles?
What are RRP, CCP, and CRP component principles?
The tension Diagram for component principles?
Clean Architecture layers in Android
How to organize modules in Android?
Data flow between layers in Android?
4. 4
● Testable
● Independent of the data sources
● Independent of libraries & frameworks
● Independent of the UI
Why should one use a Clean Architecture
5. 5
“Clean Architecture separates the application code into layers and these layers define the
Separation of Concerns inside the codebase.”
Uncle Bob
What is clean architecture
6. 6
Characteristics of a Clean Architecture
Entities
Use Cases
Controllers
G
ateways
P
r
e
s
e
n
t
e
r
s
DB
UI
Web
External
Interfaces
Devices
■ Enterprise Business Rules
■ Application Business Rules
■ Interface Adapters
■ Framework & Drivers
7. 7
● Single Responsibility Principle
● Open-Closed Principle
● Liskov Substitution Principle
● Interface Segregation Principle
● Dependency Inversion Principle
What are SOLID design principles
8. 8
■ A class should have only one reason to change.
What is Single-Responsibility principle
9. 9
fun onBind(trip: Trip) {
itemView.start_name.text = trip.start
itemView.destination_name.text = trip.destination
val formattedDate = DateFormat.getDateInstance().format(
trip.departureTime,
StringBuffer("departure Time : "),
FieldPosition(0)
)
itemView.departure_time.text = formattedDate
}
Bad Example
10. 10
fun onBind(trip: Trip) {
with(itemView) {
start_name.text = trip.start
destination_name.text = trip.destination
departure_time.text = dateUtil.formatDate(trip.departureTime)
}
}
Good Example
11. 11
■ Software entities such as classes, functions, modules should be open
for extension but not modification.
What is Open-closed principle
12. 12
class TimeGreeting(val time :String) {
fun greetingFromTimeOfDay(): String = when (time) {
"Morning" -> {
"Good Morning, sir."
}
"Afternoon" -> {
"Good Afternoon, sir."
}
"Evening" -> {
"Good Evening, sir."
}
else -> {
"Good Night, sir."
}
}
}
Bad Example
13. 13
Good Example
interface Time {
fun greet(): String
}
class Morning : Time {
override fun greet(): String {
return "Good morning, sir."
}
}
class Afternoon : Time {
override fun greet(): String {
return "Good afternoon, sir."
}
}
class Evening : Time {
override fun greet(): String {
return "Good evening, sir."
}
}
class Night : Time {
override fun greet(): String {
return "Good night, sir."
}
}
class TimeGreeting(val time: Time) {
fun greetingFromTimeOfDay(): String =
time.greet()
}
14. 14
■ Child classes should never break the parent class’ type definitions.
What is Liskov Substitution principle
15. 15
open class Rectangle {
private var width: Int = 0
private var height: Int = 0
open fun setWidth(newWidth: Int) {
width = newWidth
}
open fun setHeight(newHeight: Int) {
height = newHeight
}
fun getArea(): Int = width * height
}
Bad Example
val rectangle : Rectangle = Square()
rectangle.setWidth(3)
rectangle.setHeight(5)
rectangle.getArea()
class Square : Rectangle() {
override fun setWidth(newWidth: Int) {
super.setWidth(newWidth)
super.setHeight(newWidth)
}
override fun setHeight(newHeight: Int) {
super.setWidth(newHeight)
super.setHeight(newHeight)
}
}
16. 16
interface Shape {
fun getArea(): Int
}
class Square: Shape {
private var diameter: Int = 0
fun setDiameter(diameter: Int) {
this.diameter = diameter
}
override fun getArea(): Int {
return diameter * diameter
}
}
class Rectangle: Shape {
private var width: Int = 0
private var height: Int = 0
fun setWidth(width: Int) {
this.width = width
}
fun setHeight(height: Int) {
this.height = height
}
override fun getArea(): Int {
return width * height
}
}
Good Example
17. 17
■ The interface-segregation principle (ISP) states that no client should be forced
to depend on methods it does not use.
What is Interface segregation principle
18. 18
interface OnClickListener {
fun onClick()
fun onLongClick()
}
class Button: OnClickListener {
override fun onClick() {}
override fun onLongCLick() {
//does not support Long clicks
}
}
class LongClickButton: OnClickListener{
override fun onClick() {
// does not support short clicks
}
override fun onLongCLick() {}
}
Bad Example
19. 19
interface OnClickListener {
fun onClick()
}
interface OnLongClickListener {
fun onLongCLick()
}
class Button: OnClickListener {
override fun onClick() {}
}
class LongClickButton: OnLongClickListener {
override fun onLongCLick() {}
}
Good Example
20. 20
■ 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 is Dependency Inversion principle
21. 21
class WhatsUpMessage {
var whatsUpText:String = ""
}
class MessageBoard {
var message: WhatsUpMessage? = null
fun printMessage() {
println(message?.whatsUpText)
}
}
Bad Example
22. 22
interface IMessage {
fun printMessage()
}
class WhatsUpMessage:IMessage{
override fun printMessage() {}
}
class FaceBookMessage:IMessage{
override fun printMessage() {}
}
class MessageBoard {
var message: IMessage? = null
fun printMessage(){
message?.printMessage()
}
}
Good Example
23. 23
● RRP: The Reuse/Release Equivalence Principle
● CCP: The Common Closure Principle
● CRP: The Common Reuse Principle
What are RRP, CCP, and CRP component principles
24. 24
❏ The granularity of reuse is the granularity of release
What is The Reuse/Release Equivalence Principle
25. 25
❏ Gather into components those classes that change for the same reasons and at the same times
(related to SRP)
What is The Common Closure Principle
26. 26
❏ Don't force users of a component to depend on things they don't need (related to ISP)
What is The Common Reuse Principle
27. 27
The Tension Diagram For Component Cohesion
REP CCP
CRP
To many unneeded releases
To many
components change
Hard to reuse
29. 29
What are Clean Architecture layers in Android
■ Domain layer
■ Data layer
■ Presentation layer
Use Cases
Entities
Data Sources
ViewModels
Fragm
ents
A
c
t
i
v
i
t
i
e
s
Repository implementations
30. 30
How to organize Modules in clean way
Package by feature
Package by layer
32. 32
sealed class Result<out T : Any> {
data class Success<out T : Any>(val data: T) : Result<T>()
data class Failure(val exception: Exception) : Result<Nothing>()
}
Domain layer in Android
Entities
data class User( val userId: String,
val displayName: String)
33. 33
abstract class UseCase<out Type, in Params> where Type : Any {
abstract suspend operator fun invoke(params: Params): Result<Type>
}
Domain layer in Android
Use Cases
class LoginUseCase(private val loginRepository
: LoginRepository) :
UseCase<User, LoginUseCase.Params>() {
override suspend operator fun invoke(params: Params): Result<User> =
loginRepository
.login(password = params
.password, userName = params
.userName)
data class Params(val userName: String, val password: String)
}
35. 35
data class LoginInteractors(
val loginUseCase: LoginUseCase,
val validateUserNameUseCase: ValidateUserNameUseCase,
val validatePasswordUseCase: ValidatePasswordUseCase
)
Domain layer in Android
Interactors
37. 37
interface UserMapper {
fun mapToUser(loggedInUser: LoggedInUser): User
}
Data layer in Android
Data layer in Android
Mappers
class UserMapperImpl() : UserMapper {
override fun mapToUser(loggedInUser: LoggedInUser): User {
return User(
userId = loggedInUser.userId,
displayName = loggedInUser.displayName
)
}
}
38. 38
class LoginDataSource {
fun login(username: String, password: String): LoggedInUser {
return LoggedInUser(UUID.randomUUID(), "Jane Doe")
}
}
Data layer in Android
Data Sources
39. 39
class LoginRepositoryImpl
(
private val dataSource: LoginDataSource
,
private val userMapper: UserMapper
) : LoginRepository {
override suspend fun login(userName: String, password: String): Result<User> {
return try {
val loggedInUser = dataSource.login(userName, password)
val user = userMapper.mapToUser(loggedInUser)
Result.Success(user)
} catch (e: Throwable) {
Result.Failure(IOException("Error logging in"
, e))
}
}
...
}
Data layer in Android
Repository implementations
41. 41
data class LoginViewState(
val loginFormState: LoginFormState? = null,
val loginResult: LoginResult? = null
)
Presentation layer in Android
ViewState Entities
42. 42
class LoginViewModel(
private val loginInteractors: LoginInteractors
) : ViewModel() {
private val viewState = MutableStateFlow(LoginViewState())
fun login(username: String, password: String) {
viewModelScope.launch {
val result = loginInteractors.loginUseCase.invoke(
LoginUseCase.Params(username,password)
)
val loginResult = when (result) {
is Result.Success ->
LoginResult(success = LoggedInUserView(displayName = result.data.displayName))
else -> LoginResult(error = result.error)
}
viewState.value = viewState.value.copy(loginResult = loginResult)
}
}
}
Presentation layer in Android
ViewModel
43. 43
class LoginFragment : Fragment() {
...
override fun onViewCreated(view: View, savedInstanceState
: Bundle?) {
super.onViewCreated(view, savedInstanceState
)
...
loginViewModel
.collectLatest(lifecycleScope,::collectViewState
)
}
private fun collectViewState
(viewState: LoginViewState
) {
with(viewState.loginResult){
error
?.let{showLoginFailed
(it)}
success?.let{updateUiWithUser
(it)}
}
}
}
Presentation layer in Android
Fragment
44. 44
Data flow between layers
Data layer
LoginRepositoryImpl LoginDataSource
UserMapper
Presentation layer
LoginViewModel
LoginFragment
Domain layer
LoginInteractors LoginUseCase
Result
LoginRepository
45. Many ways to implement
Boilerplate code
Not suitable for small projects
Learning curve
Pros and Cons Clean Architecture
Pros and Cons Clean Architecture
Testable
Independent of the data sources
Independent of libraries & frameworks
Independent of the UI
Pros Cons
46. 46
What is Clean Architecture
Layers of a Clean Architecture
What are SOLID design principles
What are RRP,CCP and CRP component principles
The tension Diagram for component cohesion
Clean Architecture layers in Android
How to organize modules in Android
Data flow between layers in Android
Summary
Summary
48. 48
● https://www.blog.cleancoder.com
● Clean Architecture book by Robert Cecil Martin
● Working Effectively with Legacy Code Book by Michael C. Feathers
References
Matso Abgaryan
@AbgaryanMr
linkedin.com/in/matsoab