Home Improvement: Architecture & Kotlin

39
Home Improvement: Architecture & Kotlin Jorge D. Ortiz-Fuentes @jdortiz

Transcript of Home Improvement: Architecture & Kotlin

Page 1: Home Improvement: Architecture & Kotlin

Home Improvement: Architecture & Kotlin

Jorge D. Ortiz-Fuentes @jdortiz

Page 2: Home Improvement: Architecture & Kotlin

#AdvArchMobile

A Canonical Examples Production

Page 3: Home Improvement: Architecture & Kotlin
Page 4: Home Improvement: Architecture & Kotlin

#AdvArchMobile

Disclaimer

Page 5: Home Improvement: Architecture & Kotlin
Page 6: Home Improvement: Architecture & Kotlin

#AdvArchMobile

Agenda

★Architecture

★Kotlin

★Recap

Page 7: Home Improvement: Architecture & Kotlin

Clean Architecture

Page 8: Home Improvement: Architecture & Kotlin

Persistance FW

View

Netw

ork

Location FW

Presenter

Entity Gateway

Clean Architecture

Interactor

Entity

Page 9: Home Improvement: Architecture & Kotlin

Dependency Inversion Principle

High Level Low LevelAbstraction

Low Level

Page 10: Home Improvement: Architecture & Kotlin

#AdvArchMobile

Respectful Criticism about some Opinionated Decisions

Page 11: Home Improvement: Architecture & Kotlin

#AdvArchMobile

Architecture Components

★Awesome starting point

★View Models (inner layer) depend on the SDK:AndroidViewModel (outer layer)

Page 12: Home Improvement: Architecture & Kotlin

Kotlin

Page 13: Home Improvement: Architecture & Kotlin

#AdvArchMobile

Kotlin

★Conciseness

★Data Classes

★Extensions

★Property Delegation

★Sealed Classes

Page 14: Home Improvement: Architecture & Kotlin

Conciseness

Page 15: Home Improvement: Architecture & Kotlin

#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); }}

Page 16: Home Improvement: Architecture & Kotlin

#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); }}

Page 17: Home Improvement: Architecture & Kotlin

#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)

Page 18: Home Improvement: Architecture & Kotlin

#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()); }

Page 19: Home Improvement: Architecture & Kotlin

#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()); }

Page 20: Home Improvement: Architecture & Kotlin

#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

Page 21: Home Improvement: Architecture & Kotlin

Data Classes

Page 22: Home Improvement: Architecture & Kotlin

#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" }

Page 23: Home Improvement: Architecture & Kotlin

#AdvArchMobile

But

★Not a value type

★No defensive copy

Page 24: Home Improvement: Architecture & Kotlin

#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) } }

Page 25: Home Improvement: Architecture & Kotlin

Extensions

Page 26: Home Improvement: Architecture & Kotlin

#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" }}

Page 27: Home Improvement: Architecture & Kotlin

#AdvArchMobile

But

★A class cannot implement an interface using extensions

★Methods are declared independently

Page 28: Home Improvement: Architecture & Kotlin

Property Delegation

Page 29: Home Improvement: Architecture & Kotlin

#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) } }

Page 30: Home Improvement: Architecture & Kotlin

#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>()

Page 31: Home Improvement: Architecture & Kotlin

Sealed Classes

Page 32: Home Improvement: Architecture & Kotlin

Enum considered

harmful

Page 33: Home Improvement: Architecture & Kotlin

#AdvArchMobile

Avoid Enums

★Space

★Performance Seale

d Clas

ses

Too!

Page 34: Home Improvement: Architecture & Kotlin

#AdvArchMobile

Don’t*sealed class Interactor { class ShowProducts(val completion: ()->Unit): Interactor() {} class DeleteProduct(id: String, completion: ()->Unit) {} }

when (interactor) { is ShowProducts -> … }

Page 35: Home Improvement: Architecture & Kotlin

#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>() {//…} }

Page 36: Home Improvement: Architecture & Kotlin

Recap

Page 37: Home Improvement: Architecture & Kotlin

#AdvArchMobile

Recap

★Kotlin makes advanced architectures easier

★ Learn your options and choose

★Still learning the idioms, work with the community

Page 38: Home Improvement: Architecture & Kotlin

Thank You!

Page 39: Home Improvement: Architecture & Kotlin

@jdortiz #AdvArchMobile