Home Improvement: Architecture & Kotlin
-
Upload
jorge-ortiz -
Category
Software
-
view
99 -
download
4
Transcript of Home Improvement: Architecture & Kotlin
Home Improvement: Architecture & Kotlin
Jorge D. Ortiz-Fuentes @jdortiz
#AdvArchMobile
A Canonical Examples Production
#AdvArchMobile
Disclaimer
#AdvArchMobile
Agenda
★Architecture
★Kotlin
★Recap
Clean Architecture
Persistance FW
View
Netw
ork
Location FW
Presenter
Entity Gateway
Clean Architecture
Interactor
Entity
Dependency Inversion Principle
High Level Low LevelAbstraction
Low Level
#AdvArchMobile
Respectful Criticism about some Opinionated Decisions
#AdvArchMobile
Architecture Components
★Awesome starting point
★View Models (inner layer) depend on the SDK:AndroidViewModel (outer layer)
Kotlin
#AdvArchMobile
Kotlin
★Conciseness
★Data Classes
★Extensions
★Property Delegation
★Sealed Classes
Conciseness
#AdvArchMobile
Conciseness (code)@Module public class ProgrammersListModule { private ProgrammersListActivity activity; public ProgrammersListModule(ProgrammersListActivity activity) { this.activity = activity; } @Provides ProgrammersListPresenter provideProgrammersListPresenter(ShowProgrammersListUseCase useCase) { ProgrammersListPresenter presenter = new ProgrammersListPresenter(useCase); useCase.setPresenter(presenter); presenter.setView(activity); return presenter; } @Provides ProgrammersListConnector provideProgrammersListConnector() { return new ProgrammersListConnector(activity); }}
#AdvArchMobile
@Module class ProgrammersListModule(private val activity: ProgrammersListActivity) { @Provides fun provideProgrammersListPresenter(useCase: ShowProgrammersListUseCase): ProgrammersListPresenter = ProgrammersListPresenter(useCase = useCase).apply { useCase.presenter = this view = activity } @Provides fun provideProgrammersListConnector() = ProgrammersListConnector(view = activity)}
Conciseness (code)@Module public class ProgrammersListModule { private ProgrammersListActivity activity; public ProgrammersListModule(ProgrammersListActivity activity) { this.activity = activity; } @Provides ProgrammersListPresenter provideProgrammersListPresenter(ShowProgrammersListUseCase useCase) { ProgrammersListPresenter presenter = new ProgrammersListPresenter(useCase); useCase.setPresenter(presenter); presenter.setView(activity); return presenter; } @Provides ProgrammersListConnector provideProgrammersListConnector() { return new ProgrammersListConnector(activity); }}
#AdvArchMobile
@Module class ProgrammersListModule(private val activity: ProgrammersListActivity) { @Provides fun provideProgrammersListPresenter(useCase: ShowProgrammersListUseCase): ProgrammersListPresenter = ProgrammersListPresenter(useCase = useCase).apply { useCase.presenter = this view = activity } @Provides fun provideProgrammersListConnector() = ProgrammersListConnector(view = activity) }
Conciseness (code)
#AdvArchMobile
Conciseness (test)@Test public void itemIsConfiguredWithNameOfFetchedProgrammer() { ProgrammersListItemView item = Mockito.mock(ProgrammersListItemView.class); List<ProgrammerResponse> data = new ArrayList<ProgrammerResponse>() {{ add(TestUtils.createMainProgrammerResponse()); add(TestUtils.createAltProgrammerResponse()); }}; ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); sut.presentProgrammers(data); sut.configureItem(item, 1); verify(item).displayName(captor.capture()); assertEquals(TestData.programmerAltFullName, captor.getValue()); }
#AdvArchMobile
@Test fun itemIsConfiguredWithNameOfFetchedProgrammer() { val item = mock<ProgrammersListItemView>() val data = listOf(TestData.createMainProgrammerResponse(), TestData.createAltProgrammerResponse()) sut.present(programmers = data) sut.configureItem(item, 1) argumentCaptor<String>().apply { verify(item).displayName(capture()) assertEquals(TestData.altFullName, lastValue) }}
Conciseness@Test public void itemIsConfiguredWithNameOfFetchedProgrammer() { ProgrammersListItemView item = Mockito.mock(ProgrammersListItemView.class); List<ProgrammerResponse> data = new ArrayList<ProgrammerResponse>() {{ add(TestUtils.createMainProgrammerResponse()); add(TestUtils.createAltProgrammerResponse()); }}; ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); sut.presentProgrammers(data); sut.configureItem(item, 1); verify(item).displayName(captor.capture()); assertEquals(TestData.programmerAltFullName, captor.getValue()); }
#AdvArchMobile
@Test fun itemIsConfiguredWithNameOfFetchedProgrammer() { val item = mock<ProgrammersListItemView>() val data = listOf(TestData.createMainProgrammerResponse(), TestData.createAltProgrammerResponse()) sut.present(programmers = data) sut.configureItem(item, 1) argumentCaptor<String>().apply { verify(item).displayName(capture()) assertEquals(TestData.altFullName, lastValue) }}
Conciseness
Data Classes
#AdvArchMobile
Entitiesdata class Programmer( val firstName: String, val lastName: String, val emacs: Int, val caffeine: Int, val realProgrammerRating: Int, val interviewDate: Date, val favorite: Boolean) { val fullName: String get() = "$firstName $lastName" }
#AdvArchMobile
But
★Not a value type
★No defensive copy
#AdvArchMobile
Defensive Copyingdata class Entity private constructor(var private _date: Date) { companion object { fun create(date: Date): Entity = Entity(_date = Date(date.time)) } var date: Date = Date(_date.time) get() = Date(field.time) set(value) { field = Date(date.time) } }
Extensions
#AdvArchMobile
Presentation Logicfun Date.relativeDateFormat(origin: Date = Date()): String { fun differenceInDays(date1: Date, date2: Date): Long { val MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000 return (date1.time - date2.time) / MILLISECONDS_IN_A_DAY } val daysAgo = differenceInDays(origin, this) return when { daysAgo < 0 -> "In the future" daysAgo < 1 -> "Today" daysAgo < 7 -> "Less than a week ago" daysAgo < 30 -> "Less than a month ago" daysAgo < 365 -> "Less than a year ago" else -> "Long time ago" }}
#AdvArchMobile
But
★A class cannot implement an interface using extensions
★Methods are declared independently
Property Delegation
#AdvArchMobile
Observationdata class ProductRequest(var name: String, var units: Int)
class Presenter() { var request: ProductRequest by Delegates.observable(ProductRequest(name="", units=0)) { prop, old, new -> requestChanged() } fun requestChanged() { print("Request: $request”) } fun changeDoesNotTrigger() { request.name = "Something" request.units = 1 } fun completeChangeThatTriggers() { request = ProductRequest(name="Else", units= 2) } }
#AdvArchMobile
Delegating Reference Type
class WeakReferenceHolder<T, U> { private var propertyRef: WeakReference<U>? = null operator fun getValue(t: T, property: KProperty<*>): U? = propertyRef?.get() operator fun setValue(t: T, property: KProperty<*>, newValue: U?) { propertyRef = if (newValue != null) { WeakReference(newValue) } else { null } }}
var view: ProgrammersListView? by WeakReferenceHolder<ProgrammersListPresenter, ProgrammersListView>()
Sealed Classes
Enum considered
harmful
#AdvArchMobile
Avoid Enums
★Space
★Performance Seale
d Clas
ses
Too!
#AdvArchMobile
Don’t*sealed class Interactor { class ShowProducts(val completion: ()->Unit): Interactor() {} class DeleteProduct(id: String, completion: ()->Unit) {} }
when (interactor) { is ShowProducts -> … }
#AdvArchMobile
Dosealed class Result<V: Any, E: Exception> { class Success<V: Any, E: Exception>(val value: V) : Result<V, E>() {//…} class Failure<V: Any, E: Exception>(val error: E) : Result<V, E>() {//…} }
Recap
#AdvArchMobile
Recap
★Kotlin makes advanced architectures easier
★ Learn your options and choose
★Still learning the idioms, work with the community
Thank You!
@jdortiz #AdvArchMobile