Slide 1

Slide 1 text

Why do we need Clean Architecture? 1

Slide 2

Slide 2 text

2 Hello! I AM IGOR WOJDA Android Developer Advocate, Vonage @igorwojda

Slide 3

Slide 3 text

WHY DO WE NEED GOOD ARCHITECTURE? 3

Slide 4

Slide 4 text

“MVX ARCHITECTURE” 4 Controller Model Presenter Intent View ViewModel MVC MVP MVVM MVI X

Slide 5

Slide 5 text

MVVM 5 View ViewModel Model

Slide 6

Slide 6 text

MVVM + REPOSITORY 6 View ViewModel Repository DataSource DataSource

Slide 7

Slide 7 text

“ 7 We should think about our applications as group of use cases that describe the intent of the application group of plugins that give those uses cases access to outside word. Uncle Bob and

Slide 8

Slide 8 text

WHY CLEAN ARCHITECTURE? ◦ Independent of libraries & frameworks ◦ Independent of UI ◦ Independent of data sources 8 Business Logic UI Libraries & Frameworks Data sources

Slide 9

Slide 9 text

DEPENDENCY RULE 9

Slide 10

Slide 10 text

DEPENDENCY RULE 10 UI Data source ViewModel Repository Use case Entity

Slide 11

Slide 11 text

UI Data source ViewModel Repository Use case Entity DEPENDENCY RULE 11 Dependency rule Data Flow

Slide 12

Slide 12 text

UI Data source ViewModel Repository Use case Entity LAYERS 12

Slide 13

Slide 13 text

UI Data source ViewModel Repository Use case Entity Presentation Layer Domain Layer Data Layer LAYERS 13 Data Flow Dependency rule Dependency rule Data Flow

Slide 14

Slide 14 text

ViewModel View Use case Entity Repository Network Data Source Disk Data Source 14 LAYERS Presentation Layer Domain Layer Data Layer

Slide 15

Slide 15 text

DEPENDENCY INVERSION 15 UseCase Repository • Define interface • Class Repository implements this interface • Class Repository is injected into UseCase class UseCase Repository

Slide 16

Slide 16 text

16 LAYERS ViewModel View Use case Entity Repository Network Data Source Disk Data Source Dependencies Presentation Layer Domain Layer Data Layer

Slide 17

Slide 17 text

MODEL GOING WRONG 17

Slide 18

Slide 18 text

DATA MODEL 18 data class User( val id: String, val firstName: String, val lastName: String, val birthDate: String )

Slide 19

Slide 19 text

ADD GSON ANNOTATIONS 19 data class User( @PrimaryKey("id") val id: String, @SerializedName("first_name") val firstName: String, @SerializedName("last_name") val lastName: String, @SerializedName("birth_date") val birthDate: String )

Slide 20

Slide 20 text

ADD ROOM ANNOTATIONS 20 @Entity(tableName = "users") data class User( @PrimaryKey("id") @SerializedName("id") val id: String, @ColumnInfo(name = "first_name") @SerializedName("first_name") val firstName: String, @ColumnInfo(name = "last_name") @SerializedName("last_name") val lastName: String, @ColumnInfo(name = "birth_date") @SerializedName("birth_date") val birthDate: String )

Slide 21

Slide 21 text

ADD GETTERS 21 @Entity(tableName = "users") data class User( @PrimaryKey("id") @SerializedName("id") val id: String, @ColumnInfo(name = "first_name") @SerializedName("first_name") val firstName: String, @ColumnInfo(name = "last_name") @SerializedName("last_name") val lastName: String, @ColumnInfo(name = "birth_date") @SerializedName("birth_date") val birthDate: String ) { val fullName = "$firstName $lastName” val isAdult = DateTime.parse(dateOfBirth) .plusYears(18) .isAfter(DateTime.now()) }

Slide 22

Slide 22 text

THE GOOD MODEL 22 @Entity(tableName = "users") data class User( @PrimaryKey("id") @SerializedName("id") val id: String, @ColumnInfo(name = "first_name") @SerializedName("first_name") val firstName: String, @ColumnInfo(name = "last_name") @SerializedName("last_name") val lastName: String, @ColumnInfo(name = "birth_date") @SerializedName("birth_date") val birthDate: String ) { val fullName = "$firstName $lastName” val isAdult = DateTime.parse(dateOfBirth) .plusYears(18) .isAfter(DateTime.now()) }

Slide 23

Slide 23 text

SOLUTION? 23

Slide 24

Slide 24 text

DATA MODELS 24 UserPresentationModel UserModel UserNetworkModel UserRoomModel UserNetworkMapper UserRoomMapper UserViewMapper Presentation Layer Domain Layer Data Layer

Slide 25

Slide 25 text

USER NETWORK MODEL 25 Data Layer data class UserNetworkModel( @SerializedName("id") val id: String, @SerializedName("first_name") val firstName: String, @SerializedName("last_name") val lastName: String, @SerializedName("birth_date") val birthDate: String )

Slide 26

Slide 26 text

USER ROOM MODEL 26 Data Layer @Entity(tableName = "users") data class UserRoomModel( @PrimaryKey("id") val id: String, @ColumnInfo(name = "first_name") val firstName: String, @ColumnInfo(name = "last_name") val lastName: String, @SerializedName("birth_date") val birthDate: String )

Slide 27

Slide 27 text

USER MODEL 27 data class UserModel( val id: String, val firstName: String, val lastName: String, val birthDate: DateTime ) Domain Layer

Slide 28

Slide 28 text

USER PRESENTATION MODEL 28 Presentation Layer data class UserPresentationModel( val fullName: String, val birthDate: String )

Slide 29

Slide 29 text

FIRST NAME REPRESENTATION 29 UserPresentationModel UserModel UserNetworkModel UserRoomModel UserNetworkMapper UserRoomMapper UserPresentationMapper Presentation Layer Domain Layer Data Layer “Igor" “Igor" “Igor" “Igor"

Slide 30

Slide 30 text

DATE & TIME REPRESENTATION 30 UserPresentationModel UserModel UserNetworkModel UserRoomModel UserNetworkMapper UserRoomMapper UserPresentationMapper Presentation Layer Domain Layer Data Layer "20201107T170000Z" 1604407243 "11th Nov 2020” DateTime

Slide 31

Slide 31 text

MAPPERS 31

Slide 32

Slide 32 text

MAPPERS 32 UserPresentationModel UserModel UserNetworkModel UserRoomModel UserNetworkMapper UserRoomMapper UserPresentationMapper Presentation Layer Domain Layer Data Layer

Slide 33

Slide 33 text

MAPPER 33 DataType1 Mapper DataType2 UserNetworkModel Mapper UserModel

Slide 34

Slide 34 text

IMPLEMENTATION 34

Slide 35

Slide 35 text

MAPPER AS INFERFACE 35 interface Mapper { fun map(from: FROM): TO } class UserNetworkModelMapper : Mapper { override fun map(UserNetworkModel: UserNetworkModel) = UserModel( id = UserNetworkModel.id, firstName = UserNetworkModel.firstName, lastName = UserNetworkModel.lastName, birthDate = DateTime.parse(UserNetworkModel.birthDate) ) }

Slide 36

Slide 36 text

MAPPER AS INFERFACE 36 interface Mapper { fun map(from: FROM): TO } class UserNetworkModelMapper : Mapper { override fun map(UserNetworkModel: UserNetworkModel) = UserModel( id = UserNetworkModel.id, firstName = UserNetworkModel.firstName, lastName = UserNetworkModel.lastName, birthDate = DateTime.parse(UserNetworkModel.birthDate) ) }

Slide 37

Slide 37 text

MAPPER AS INFERFACE 37 interface Mapper { fun map(from: FROM): TO } class UserNetworkModelMapper : Mapper { override fun map(UserNetworkModel: UserNetworkModel) = UserModel( id = UserNetworkModel.id, firstName = UserNetworkModel.firstName, lastName = UserNetworkModel.lastName, birthDate = DateTime.parse(UserNetworkModel.birthDate) ) }

Slide 38

Slide 38 text

MAPPER AS EXTENSION 38 val usedModel = userNetworkModel.toDomainModel() fun UserNetworkModel.toDomainModel() = UserModel( id = this.id, firstName = this.firstName, lastName = this.lastName, birthDate = DateTime.parse(UserNetworkModel.birthDate) )

Slide 39

Slide 39 text

ViewModel View Use case Entity Repository Network Data Source Disk Data Source 39 VIEW MODEL IMPLEMENTATION Presentation Layer Domain Layer Data Layer Abstractions

Slide 40

Slide 40 text

ViewModel View Use case Entity Repository Network Data Source Disk Data Source 40 VIEW MODEL IMPLEMENTATION Presentation Layer Domain Layer Data Layer Abstractions

Slide 41

Slide 41 text

VIEW MODEL IMPLEMENTATION 41 class UsersViewModel(val getUsersUseCase: GetUsersUseCase) : ViewModel {
 
 }

Slide 42

Slide 42 text

VIEW MODEL IMPLEMENTATION 42 class UsersViewModel(val getUsersUseCase: GetUsersUseCase) : ViewModel {
 
 fun getUsers() { 
 }
 }

Slide 43

Slide 43 text

VIEW MODEL IMPLEMENTATION 43 class UsersViewModel(val getUsersUseCase: GetUsersUseCase) : ViewModel {
 
 fun getUsers() { viewModelScope.launch {
 getUsersUseCase.execute().also { }
 }
 }

Slide 44

Slide 44 text

VIEW MODEL IMPLEMENTATION 44 class UsersViewModel(val getUsersUseCase: GetUsersUseCase) : ViewModel {
 
 fun getUsers() { viewModelScope.launch {
 getUsersUseCase.execute().also { result -> when (result) { is Success -> { // do something } is Error -> { // do something } } }
 }
 }

Slide 45

Slide 45 text

VIEW MODEL IMPLEMENTATION 45 class UsersViewModel(val getUsersUseCase: GetUsersUseCase) : ViewModel {
 
 fun getUsers() { viewModelScope.launch {
 getUsersUseCase.execute().also { result -> when (result) { is Success -> { // do something } is Error -> { // do something } } }
 }
 }

Slide 46

Slide 46 text

Presentation Layer Domain Layer Data Layer ViewModel View Use case Entity Repository Network Data Source Disk Data Source 46 USE CASE IMPLEMENTATION Abstractions

Slide 47

Slide 47 text

USE CASE IMPLEMENTATION 47 class GetAdultUsersUseCase(val UsersRepository: UsersRepository) { }

Slide 48

Slide 48 text

USE CASE IMPLEMENTATION 48 class GetAdultUsersUseCase(val UsersRepository: UsersRepository) { suspend fun execute(): List = UserRepository.getUsers() }

Slide 49

Slide 49 text

USE CASE IMPLEMENTATION 49 class GetAdultUsersUseCase(val UsersRepository: UsersRepository) { suspend fun execute(): List = UserRepository.getUsers() .filter { it.adult } }

Slide 50

Slide 50 text

USE CASE IMPLEMENTATION 50 class GetAdultUsersUseCase(val UsersRepository: UsersRepository) { suspend fun execute(): List = UserRepository.getUsers() .filter { it.adult } }

Slide 51

Slide 51 text

Presentation Layer Domain Layer Data Layer ViewModel View Use case Entity Repository Network Data Source Disk Data Source 51 USE CASE IMPLEMENTATION Abstractions

Slide 52

Slide 52 text

ENTITY IMPLEMENTATION 52 data class UserModel( val id: String, val firstName: String, val lastName: String, val birthDate: DateTime )

Slide 53

Slide 53 text

Presentation Layer Domain Layer Data Layer ViewModel View Use case Entity Repository Network Data Source Disk Data Source 53 USE CASE IMPLEMENTATION Abstractions

Slide 54

Slide 54 text

ABSTRACIONS IMPLEMENTATION 54 interface UsersRepository() { fun getUsers(): List }

Slide 55

Slide 55 text

Presentation Layer Domain Layer Data Layer ViewModel View Use case Entity Repository Network Data Source Disk Data Source 55 REPOSITORY IMPLEMENTATION Abstractions

Slide 56

Slide 56 text

class UsersRepositoryImpl( private val usersRetrofitService: UsersRetrofitService ) : UsersRepository { } REPOSITORY IMPLEMENTATION 56 UserRepository UserRepositoryImpl

Slide 57

Slide 57 text

class UsersRepositoryImpl( private val usersRetrofitService: UsersRetrofitService ) : UsersRepository { override suspend fun getUsers() = usersRetrofitService.getUsersAsync() } REPOSITORY IMPLEMENTATION 57

Slide 58

Slide 58 text

class UsersRepositoryImpl( private val usersRetrofitService: UsersRetrofitService ) : UsersRepository { override suspend fun getUsers() = usersRetrofitService.getUsersAsync() .map { it.toDomainModel() } } REPOSITORY IMPLEMENTATION 58

Slide 59

Slide 59 text

class UsersRepositoryImpl( private val usersRetrofitService: UsersRetrofitService ) : UsersRepository { override suspend fun getUsers() = usersRetrofitService.getUsersAsync() .map { it.toDomainModel() } } REPOSITORY IMPLEMENTATION 59

Slide 60

Slide 60 text

LAYERS IMPLEMENTATION 60 Layers in a single module Layers inside the feature modules Layer per module

Slide 61

Slide 61 text

61 DATA FLOW View ViewModel Use case Repository Network Data Source SQLite Data Source Mapper

Slide 62

Slide 62 text

62 MODULARITY Presentation Layer Domain Layer Data Layer ViewModel Business Logic View GridView Recycler View TabLayout BottomBar User Repository Tracking Repository SQLite Volley Firebase Retrofit MixPanel Room

Slide 63

Slide 63 text

63 TESTING Presentation Layer Domain Layer Data Layer Use Case Repository Entity ViewModel View

Slide 64

Slide 64 text

64 TESTING Presentation Layer Domain Layer Data Layer ViewModel Use Case View Repository Entity Mapper Mock Room Data Source Mock Network Data Source Mock Repository Mock ViewModel Mock Mapper Mock Mapper Mock Mapper Mock Entity Entity Entity Use Case Mock Mapper Mock

Slide 65

Slide 65 text

65 PROS AND CONS • Great for long living projects • Great for big teams • Maintainability • Scalability • Parallel teams • Maintainable • Testable Cons Pros • Boilerplate code • Not suitable for all projects • Multiple ways to implement

Slide 66

Slide 66 text

66 REFERENCES Articles • The Clean Architecture https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean- architecture.html • Multiple ways of defining Clean Architecture layers https://proandroiddev.com/ multiple-ways-of-defining-clean-architecture-layers-bbb70afa5d4a Android projects • https://github.com/igorwojda/android-showcase • https://github.com/android10/Android-CleanArchitecture-Kotlin • https://github.com/bufferapp/android-clean-architecture-boilerplate • https://github.com/bufferapp/clean-architecture-components-boilerplate Talks • https://youtu.be/Nsjsiz2A9mg • https://youtu.be/3Mq5newPdck • https://youtu.be/-sEmrJOk1uI • https://youtu.be/su34EYeQ90E

Slide 67

Slide 67 text

67 ANY QUESTIONS? Find me on twitter @IgorWojda Thanks!

Slide 68

Slide 68 text

68 Special thanks to SlidesCarnival who made and released these awesome presentation template for free. CREDITS