“ We should think about our applications as group of use cases that describe the intent of the application and group of plugins that give those uses cases access to outside word. Uncle Bob
Why Clean Architecture? ◦ Independent of libraries & frameworks ◦ Independent of UI ◦ Independent of data sources Business Logic UI Frameworks Data sources
Dependency inversion UseCase Repository • Define interface • Class Repository implements this interface • Class Repository is injected into class UseCase UseCase Repository
Data model (GSON) data class PatientModel( @SerializedName("id") val id: String, @SerializedName("first_name") val firstName: String, @SerializedName("last_name") val lastName: String, @SerializedName("date_of_birth") val dateOfBirth: String )
Data model (GSON + Room) @Entity(tableName = "patients") data class PatientModel( @SerializedName("id") @PrimaryKey val id: String, @SerializedName("first_name") @ColumnInfo(name = "first_name") val firstName: String, @SerializedName("last_name") @ColumnInfo(name = "last_name") val lastName: String, @SerializedName("date_of_birth") @ColumnInfo(name = "date_of_birth") val dateOfBirth: String )
Separate data model data class PatientNetworkModel( @SerializedName("id") val id: String, @SerializedName("first_name") val firstName: String, @SerializedName("last_name") val lastName: String, @SerializedName("date_of_birth") val dateOfBirth: String ) Data Layer
Separate data model Data Layer @Entity(tableName = "patients") data class PatientRoomModel( @PrimaryKey val id: String, @ColumnInfo(name = "first_name") val firstName: String, @ColumnInfo(name = "last_name") val lastName: String, @ColumnInfo(name = "date_of_birth") val dateOfBirth: String )
Use case (Interactor) class PatientsUseCase @Inject constructor(val patientRepository: PatientRepository, val schedulers: Schedulers) { fun execute(): Single> = patientRepository.getPatients() .observeOn(schedulers.main) .subscribeOn(schedulers.io) .filter { it.adult } }
Use case (Interactor) class PatientsUseCase @Inject constructor(val patientRepository: PatientRepository, val schedulers: Schedulers) { fun execute(): Single> = patientRepository.getPatients() .observeOn(schedulers.main) .subscribeOn(schedulers.io) .filter { it.adult } }
Use case (Interactor) class PatientsUseCase @Inject constructor(val patientRepository: PatientRepository, val schedulers: Schedulers) { fun execute(): Single> = patientRepository.getPatients() .observeOn(schedulers.main) .subscribeOn(schedulers.io) .filter { it.adult } }
Pros and cons Boilerplate code Not suitable for all projects Multiple ways to implement Independent of Frameworks and tools Well structured Maintainable Modular Testable Great for big teams Issues Benefits