Pro Yearly is on sale from $80 to $50! »

Droidcon Americas 2020. Why We Need Clean Architecture

E481624463bdb1c97c46e2155408acb4?s=47 Igor Wojda
November 17, 2020

Droidcon Americas 2020. Why We Need Clean Architecture

"You can’t anticipate everything that’s going to change in the software but you can try to build in enough flexibility that it’s likely to adapt to most changes"

Application architecture defines the way we develop, modularise, test, scale, and maintain our application. A good architecture allows us to deal with never-ending stream requirements by providing enough flexibility and solid protection against regression.

Igor will introduce the concept of Clean Architecture in the context of Android Development, discuss it's benefits, helps you understand how Clean Architecture fits into modern Android application development.

E481624463bdb1c97c46e2155408acb4?s=128

Igor Wojda

November 17, 2020
Tweet

Transcript

  1. Why do we need Clean Architecture? 1

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

    @igorwojda
  3. WHY DO WE NEED GOOD ARCHITECTURE? 3

  4. “MVX ARCHITECTURE” 4 Controller Model Presenter Intent View ViewModel MVC

    MVP MVVM MVI X
  5. MVVM 5 View ViewModel Model

  6. MVVM + REPOSITORY 6 View ViewModel Repository DataSource DataSource

  7. “ 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
  8. WHY CLEAN ARCHITECTURE? ◦ Independent of libraries & frameworks ◦

    Independent of UI ◦ Independent of data sources 8 Business Logic UI Libraries & Frameworks Data sources
  9. DEPENDENCY RULE 9

  10. DEPENDENCY RULE 10 UI Data source ViewModel Repository Use case

    Entity
  11. UI Data source ViewModel Repository Use case Entity DEPENDENCY RULE

    11 Dependency rule Data Flow
  12. UI Data source ViewModel Repository Use case Entity LAYERS 12

  13. UI Data source ViewModel Repository Use case Entity Presentation Layer

    Domain Layer Data Layer LAYERS 13 Data Flow Dependency rule Dependency rule Data Flow
  14. ViewModel View Use case Entity Repository Network Data Source Disk

    Data Source 14 LAYERS Presentation Layer Domain Layer Data Layer
  15. DEPENDENCY INVERSION 15 UseCase Repository • Define interface • Class

    Repository implements this interface • Class Repository is injected into UseCase class UseCase Repository
  16. 16 LAYERS ViewModel View Use case Entity Repository Network Data

    Source Disk Data Source Dependencies Presentation Layer Domain Layer Data Layer
  17. MODEL GOING WRONG 17

  18. DATA MODEL 18 data class User( val id: String, val

    firstName: String, val lastName: String, val birthDate: String )
  19. 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 )
  20. 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 )
  21. 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()) }
  22. 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()) }
  23. SOLUTION? 23

  24. DATA MODELS 24 UserPresentationModel UserModel UserNetworkModel UserRoomModel UserNetworkMapper UserRoomMapper UserViewMapper

    Presentation Layer Domain Layer Data Layer
  25. 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 )
  26. 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 )
  27. USER MODEL 27 data class UserModel( val id: String, val

    firstName: String, val lastName: String, val birthDate: DateTime ) Domain Layer
  28. USER PRESENTATION MODEL 28 Presentation Layer data class UserPresentationModel( val

    fullName: String, val birthDate: String )
  29. FIRST NAME REPRESENTATION 29 UserPresentationModel UserModel UserNetworkModel UserRoomModel UserNetworkMapper UserRoomMapper

    UserPresentationMapper Presentation Layer Domain Layer Data Layer “Igor" “Igor" “Igor" “Igor"
  30. DATE & TIME REPRESENTATION 30 UserPresentationModel UserModel UserNetworkModel UserRoomModel UserNetworkMapper

    UserRoomMapper UserPresentationMapper Presentation Layer Domain Layer Data Layer "20201107T170000Z" 1604407243 "11th Nov 2020” DateTime
  31. MAPPERS 31

  32. MAPPERS 32 UserPresentationModel UserModel UserNetworkModel UserRoomModel UserNetworkMapper UserRoomMapper UserPresentationMapper Presentation

    Layer Domain Layer Data Layer
  33. MAPPER 33 DataType1 Mapper DataType2 UserNetworkModel Mapper UserModel

  34. IMPLEMENTATION 34

  35. MAPPER AS INFERFACE 35 interface Mapper<in FROM, out TO> {

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

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

    fun map(from: FROM): TO } class UserNetworkModelMapper : Mapper<UserNetworkModel, UserModel> { override fun map(UserNetworkModel: UserNetworkModel) = UserModel( id = UserNetworkModel.id, firstName = UserNetworkModel.firstName, lastName = UserNetworkModel.lastName, birthDate = DateTime.parse(UserNetworkModel.birthDate) ) }
  38. 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) )
  39. ViewModel View Use case Entity Repository Network Data Source Disk

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

    Data Source 40 VIEW MODEL IMPLEMENTATION Presentation Layer Domain Layer Data Layer Abstractions
  41. VIEW MODEL IMPLEMENTATION 41 class UsersViewModel(val getUsersUseCase: GetUsersUseCase) : ViewModel

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

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

    {
 
 fun getUsers() { viewModelScope.launch {
 getUsersUseCase.execute().also { }
 }
 }
  44. 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 } } }
 }
 }
  45. 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 } } }
 }
 }
  46. Presentation Layer Domain Layer Data Layer ViewModel View Use case

    Entity Repository Network Data Source Disk Data Source 46 USE CASE IMPLEMENTATION Abstractions
  47. USE CASE IMPLEMENTATION 47 class GetAdultUsersUseCase(val UsersRepository: UsersRepository) { }

  48. USE CASE IMPLEMENTATION 48 class GetAdultUsersUseCase(val UsersRepository: UsersRepository) { suspend

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

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

    fun execute(): List<User> = UserRepository.getUsers() .filter { it.adult } }
  51. Presentation Layer Domain Layer Data Layer ViewModel View Use case

    Entity Repository Network Data Source Disk Data Source 51 USE CASE IMPLEMENTATION Abstractions
  52. ENTITY IMPLEMENTATION 52 data class UserModel( val id: String, val

    firstName: String, val lastName: String, val birthDate: DateTime )
  53. Presentation Layer Domain Layer Data Layer ViewModel View Use case

    Entity Repository Network Data Source Disk Data Source 53 USE CASE IMPLEMENTATION Abstractions
  54. ABSTRACIONS IMPLEMENTATION 54 interface UsersRepository() { fun getUsers(): List<User> }

  55. Presentation Layer Domain Layer Data Layer ViewModel View Use case

    Entity Repository Network Data Source Disk Data Source 55 REPOSITORY IMPLEMENTATION Abstractions
  56. class UsersRepositoryImpl( private val usersRetrofitService: UsersRetrofitService ) : UsersRepository {

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

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

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

    override suspend fun getUsers() = usersRetrofitService.getUsersAsync() .map { it.toDomainModel() } } REPOSITORY IMPLEMENTATION 59
  60. LAYERS IMPLEMENTATION 60 Layers in a single module Layers inside

    the feature modules Layer per module
  61. 61 DATA FLOW View ViewModel Use case Repository Network Data

    Source SQLite Data Source Mapper
  62. 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
  63. 63 TESTING Presentation Layer Domain Layer Data Layer Use Case

    Repository Entity ViewModel View
  64. 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
  65. 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
  66. 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
  67. 67 ANY QUESTIONS? Find me on twitter @IgorWojda Thanks!

  68. 68 Special thanks to SlidesCarnival who made and released these

    awesome presentation template for free. CREDITS