Architecture Patterns in Practice
with Kotlin
Jorge Juan Barroso Carmona
Developer and GDE
[email protected]
@flipper83
Slide 2
Slide 2 text
Testing for android & iOS.
Trainings
Architecture, Patterns and
principles for Android & iOS.
Mastering Git.
Advanced mobile development.
Testing for android & iOS.
Architecture, Patterns and
principles for Android & iOS.
Companies For Everybody
Slide 3
Slide 3 text
Sergio Gutierrez
Senior Full Stack Engineer
Pedro Gomez
Senior Full Stack Engineer
Davide Mendolia
Senior Full Stack
Engineer
Fran Toro
Senior Mobile Engineer
Slide 4
Slide 4 text
Adam Tornhill
“Computer languages differ not so much
in what they make possible, but in what
they make easy.”
Larry Wall
Slide 5
Slide 5 text
https://github.com/Karumi/SuperHeroesKotlin
Slide 6
Slide 6 text
No content
Slide 7
Slide 7 text
Why
Slide 8
Slide 8 text
Write readable
code
Slide 9
Slide 9 text
Testable
Slide 10
Slide 10 text
Remove boilerplate
code
Slide 11
Slide 11 text
Easy to change
and evolve
Slide 12
Slide 12 text
Kotlin
to avoid Java
problems
Slide 13
Slide 13 text
Activity
Fragment
Service
View
…
Presenter
View
Use Case
Rich Model
Repository
DataSource
Api
Retrofit
SharedPref
Api
Sync Execution
Slide 14
Slide 14 text
Android is a framework
based on inheritance
Slide 15
Slide 15 text
Activity
Fragment
Service
View
…
Presenter
View
Domain
Model
Presentation
Model
Use Case
Slide 16
Slide 16 text
Adam Tornhill
class MainActivity : BaseActivity(),
SuperHeroesPresenter.View {
override val presenter: SuperHeroesPresenter by
injector.instance()
private lateinit var adapter: SuperHeroesAdapter
override val layoutId: Int = R.layout.main_activity
override val toolbarView: Toolbar
get() = toolbar
/*…*/
}
Slide 17
Slide 17 text
Adam Tornhill
class MainActivity : BaseActivity(),
SuperHeroesPresenter.View {
override val presenter: SuperHeroesPresenter by
injector.instance()
private lateinit var adapter: SuperHeroesAdapter
override val layoutId: Int = R.layout.main_activity
override val toolbarView: Toolbar
get() = toolbar
/*…*/
}
Computed properties
Slide 18
Slide 18 text
Adam Tornhill
abstract class BaseActivity : KodeinAppCompatActivity(),
LifecyclePublisher by lifeCycleLinker {
abstract val layoutId: Int
abstract val presenter: LifecycleSubscriber
abstract val toolbarView: Toolbar
abstract val activityModules: Module
/*…*/
}
Slide 19
Slide 19 text
Adam Tornhill
class MainActivity : BaseActivity(),
SuperHeroesPresenter.View {
override val presenter: SuperHeroesPresenter by
injector.instance()
private lateinit var adapter: SuperHeroesAdapter
override val layoutId: Int = R.layout.main_activity
override val toolbarView: Toolbar
get() = toolbar
/*…*/
}
override
properties
Slide 20
Slide 20 text
Adam Tornhill
class MainActivity : BaseActivity(),
SuperHeroesPresenter.View {
override val presenter: SuperHeroesPresenter by
injector.instance()
private lateinit var adapter: SuperHeroesAdapter
override val layoutId: Int = R.layout.main_activity
override val toolbarView: Toolbar
get() = toolbar
/*…*/
}
Slide 21
Slide 21 text
Adam Tornhill
abstract class BaseActivity : KodeinAppCompatActivity(),
LifecyclePublisher by lifeCycleLinker {
abstract val layoutId: Int
abstract val presenter: LifecycleSubscriber
abstract val toolbarView: Toolbar
abstract val activityModules: Module
/*…*/
}
Delegate
Slide 22
Slide 22 text
Adam Tornhill
abstract class BaseActivity : KodeinAppCompatActivity(),
LifecyclePublisher by lifeCycleLinker {
abstract val presenter: LifecycleSubscriber
/*…*/
override fun onResume() {
super.onResume()
update()
}
override fun onDestroy() {
unregister(presenter)
super.onDestroy()
}
delegate to..
Slide 23
Slide 23 text
LifecycleLinker :
LifecyclePublisher
private val receivers =
ArrayList()
override fun unregister
(subscriber: LifecycleSubscriber) {
receivers.remove(subscriber)
}
override fun update() {
receivers
.forEach
(LifecycleSubscriber::update)
}
/*…*/
}
Adam Tornhill
override fun onResume() {
super.onResume()
update()
}
override fun onDestroy() {
unregister(presenter)
super.onDestroy()
}
BaseActivity :
LifecyclePublisher
by lifeCycleLinker
Slide 24
Slide 24 text
Avoid memory leaks
Slide 25
Slide 25 text
Big problem
using passive Views on
presenters
Slide 26
Slide 26 text
Activity
Fragment
Service
View
…
Presenter
View
Domain
Model
Presentation
Model
Use Case
Slide 27
Slide 27 text
Adam Tornhill
class SuperHeroesPresenter(
view: View,
private val getSuperHeroes:
GetSuperHeroes) :LifecycleSubscriber {
private val view: View? by weak(view)
/*…*/
}
Slide 28
Slide 28 text
Adam Tornhill
class SuperHeroesPresenter(
view: View,
private val getSuperHeroes:
GetSuperHeroes) :LifecycleSubscriber {
private val view: View? by weak(view)
/*…*/
}
argument
properties
Slide 29
Slide 29 text
Adam Tornhill
class SuperHeroesPresenter(
view: View,
private val getSuperHeroes:
GetSuperHeroes) :LifecycleSubscriber {
private val view: View? by weak(view)
/*…*/
} argument
properties
delegate
property
Slide 30
Slide 30 text
fun weak(value: T) = WeakRef(value)
class WeakRef(value: T) {
private val weakReference: WeakReference =
WeakReference(value)
operator fun getValue(thisRef: Any, property:
KProperty<*>): T? = weakReference.get()
} Adam Tornhill
class SuperHeroesPresenter(
view: View,
private val getSuperHeroes:
GetSuperHeroes) :LifecycleSubscriber {
private val view: View? by weak(view)
/*…*/
} argument
poperties
Slide 31
Slide 31 text
fun weak(value: T) = WeakRef(value)
class WeakRef(value: T) {
private val weakReference: WeakReference =
WeakReference(value)
operator fun getValue(thisRef: Any, property:
KProperty<*>): T? = weakReference.get()
} Adam Tornhill
class SuperHeroesPresenter(
view: View,
private val getSuperHeroes:
GetSuperHeroes) :LifecycleSubscriber {
private val view: View? by weak(view)
/*…*/
}
Slide 32
Slide 32 text
class SuperHeroesPresenter(
view: View,
private val getSuperHeroes: GetSuperHeroes) :
LifecycleSubscriber {
private val view: View? by weak(view)
override fun update() {
view?.showLoading()
refreshSuperHeroes()
}
We’re acting
(Optional)
Slide 33
Slide 33 text
fun weak(value: T) = WeakRef(value)
class WeakRef(value: T) {
private val weakReference: WeakReference =
WeakReference(value)
operator fun getValue(thisRef: Any, property:
KProperty<*>): T? = weakReference.get()
} Adam Tornhill
class SuperHeroesPresenter(
view: View,
private val getSuperHeroes:
GetSuperHeroes) :LifecycleSubscriber {
private val view: View? by weak(view)
/*…*/
}
Slide 34
Slide 34 text
Working with
Errors
Slide 35
Slide 35 text
Only one code path
Slide 36
Slide 36 text
Either Monad
Slide 37
Slide 37 text
A monad is just a monoid
in the category
of endofunctors
Slide 38
Slide 38 text
No content
Slide 39
Slide 39 text
Either
Slide 40
Slide 40 text
Either
Error Success
Slide 41
Slide 41 text
class NetworkSuperHeroDataSource(
private val apiClient: CharacterApiClient) :
SuperHeroDataSource {
override fun get(key: String):
Either =
try {
mapResponse(apiClient.getCharacter(key)) {
mapSuperHero(it)
}
} catch (exception: Exception) {
Either.left(mapException(exception))
}
/*…*/
}
Adam Tornhill
Slide 42
Slide 42 text
class NetworkSuperHeroDataSource(
private val apiClient: CharacterApiClient) :
SuperHeroDataSource {
override fun get(key: String):
Either =
try {
mapResponse(apiClient.getCharacter(key)) {
mapSuperHero(it)
}
} catch (exception: Exception) {
Either.left(mapException(exception))
}
/*…*/
}
Adam Tornhill
Slide 43
Slide 43 text
class NetworkSuperHeroDataSource(
private val apiClient: CharacterApiClient) :
SuperHeroDataSource {
override fun get(key: String):
Either =
try {
mapResponse(apiClient.getCharacter(key)) {
mapSuperHero(it)
}
} catch (exception: Exception) {
Either.left(mapException(exception))
}
/*…*/
}
Adam Tornhill
Slide 44
Slide 44 text
GetSuperHeroes:
fun invoke(): Either>
SuperHeroesRepository:
fun getAllSuperHeroes(): Either>
SuperHeroDataSource:
fun getAll(): Either>
Adam Tornhill
Slide 45
Slide 45 text
GetSuperHeroes:
fun invoke(): Either>
SuperHeroesRepository:
fun getAllSuperHeroes(): Either>
SuperHeroDataSource:
fun getAll(): Either>
Adam Tornhill
Don’t break the chain
Slide 46
Slide 46 text
private fun refreshSuperHeroes() = async {
val result = await { getSuperHeroes() }
view?.hideLoading()
when (result) {
is Right -> showSuperHeroes(result.r)
is Left -> view?.showError(result.l)
}
}
Adam Tornhill
MainPresenter:
switch on
steroids
Slide 47
Slide 47 text
private fun refreshSuperHeroes() = async {
val result = await { getSuperHeroes() }
view?.hideLoading()
when (result) {
is Right -> showSuperHeroes(result.r)
is Left -> view?.showError(result.l)
}
}
Adam Tornhill
MainPresenter:
smart cast
Slide 48
Slide 48 text
abstract class BaseActivity : KodeinAppCompatActivity(),
LifecyclePublisher by lifeCycleLinker {
/*…*/
fun showError(error: DomainError) =
Snackbar.make(toolbarView, error.asString(this),
Snackbar.LENGTH_LONG).show()
}
Adam Tornhill
Slide 49
Slide 49 text
abstract class BaseActivity : KodeinAppCompatActivity(),
LifecyclePublisher by lifeCycleLinker {
/*…*/
fun showError(error: DomainError) =
Snackbar.make(toolbarView, error.asString(this),
Snackbar.LENGTH_LONG).show()
}
Adam Tornhill
Slide 50
Slide 50 text
fun DomainError.asString(context: Context): String =
context.getString(getMessage(this))
fun getMessage(domainError: DomainError): Int =
when (domainError) {
is NotInternetDomainError ->
R.string.error_not_internet_message
is NotIndexFoundDomainError ->
R.string.error_superhero_not_found_message
is AuthDomainError ->
R.string.error_invalid_credentials
is UnknownDomainError ->
R.string.error_unknown_message
}
Adam Tornhill
extension
method
Slide 51
Slide 51 text
fun DomainError.asString(context: Context): String =
context.getString(getMessage(this))
fun getMessage(domainError: DomainError): Int =
when (domainError) {
is NotInternetDomainError ->
R.string.error_not_internet_message
is NotIndexFoundDomainError ->
R.string.error_superhero_not_found_message
is AuthDomainError ->
R.string.error_invalid_credentials
is UnknownDomainError ->
R.string.error_unknown_message
}
Adam Tornhill
Slide 52
Slide 52 text
fun DomainError.asString(context: Context): String =
context.getString(getMessage(this))
fun getMessage(domainError: DomainError): Int =
when (domainError) {
is NotInternetDomainError ->
R.string.error_not_internet_message
is NotIndexFoundDomainError ->
R.string.error_superhero_not_found_message
is AuthDomainError ->
R.string.error_invalid_credentials
is UnknownDomainError ->
R.string.error_unknown_message
}
Adam Tornhill
Compile error!!
Slide 53
Slide 53 text
sealed class DomainError
object NotInternetDomainError : DomainError()
data class UnknownDomainError(
val errorMessage: String = "Unknown Error") :
DomainError()
data class NotIndexFoundDomainError(val key: String) :
DomainError()
object AuthDomainError : DomainError()
Adam Tornhill
Slide 54
Slide 54 text
make code readable
Slide 55
Slide 55 text
Kotlin can increase
your readability
Slide 56
Slide 56 text
improve command
pattern
Slide 57
Slide 57 text
class SuperHeroesPresenter(
view: View,
private val getSuperHeroes: GetSuperHeroes) :
LifecycleSubscriber {
private fun refreshSuperHeroes() = async {
val result = await { getSuperHeroes() }
view?.hideLoading()
when (result) {
is Right -> showSuperHeroes(result.r)
is Left -> view?.showError(result.l)
}
}
/*…*/
}
Adam Tornhill
is this a method?
or is an object?
Slide 58
Slide 58 text
class GetSuperHeroes(
private val superHeroesRepository: SuperHeroRepository) {
operator fun invoke(): Either> = superHeroesRepository
.getAllSuperHeroes()
}
Adam Tornhill
Override
operators
getSuperHeroes()
Slide 59
Slide 59 text
improve domain
models
Slide 60
Slide 60 text
data class SuperHero(
val id: String,
val name: String,
val photo: String? = null,
val isAvenger: Boolean,
val description: String)
Adam Tornhill
Slide 61
Slide 61 text
data class SuperHero(
val id: String,
val name: String,
val photo: String? = null,
val isAvenger: Boolean,
val description: String)
Adam Tornhill
All domain models
in the same file
Slide 62
Slide 62 text
Concurrency
problems
Slide 63
Slide 63 text
Activity
Fragment
Service
View
…
Presenter
View
Use Case
Rich Model
Repository
DataSource
Api
Retrofit
SharedPref
Api
Sync Execution
Slide 64
Slide 64 text
Coroutines for all
Slide 65
Slide 65 text
class SuperHeroesPresenter(
view: View,
private val getSuperHeroes: GetSuperHeroes) :
LifecycleSubscriber {
private fun refreshSuperHeroes() = async {
val result = await { getSuperHeroes() }
view?.hideLoading()
when (result) {
is Right -> showSuperHeroes(result.r)
is Left -> view?.showError(result.l)
}
}
/*…*/
}
Slide 66
Slide 66 text
class SuperHeroesPresenter(
view: View,
private val getSuperHeroes: GetSuperHeroes) :
LifecycleSubscriber {
private fun refreshSuperHeroes() = async {
val result = await { getSuperHeroes() }
view?.hideLoading()
when (result) {
is Right -> showSuperHeroes(result.r)
is Left -> view?.showError(result.l)
}
}
/*…*/
}
Slide 67
Slide 67 text
class SuperHeroesPresenter(
view: View,
private val getSuperHeroes: GetSuperHeroes) :
LifecycleSubscriber {
private fun refreshSuperHeroes() = async {
val result = await { getSuperHeroes() }
view?.hideLoading()
when (result) {
is Right -> showSuperHeroes(result.r)
is Left -> view?.showError(result.l)
}
}
/*…*/
}
Slide 68
Slide 68 text
Dependency
injection
Slide 69
Slide 69 text
Dagger 2
vs
Kodein
Slide 70
Slide 70 text
class MainActivity : BaseActivity(),
SuperHeroesPresenter.View {
override val presenter: SuperHeroesPresenter by
injector.instance()
/*...*/
override val activityModules =
Module(allowSilentOverride = true) {
bind() with provider {
SuperHeroesPresenter(this@MainActivity,
instance())
}
bind() with provider{
GetSuperHeroes(instance()) }
}
Slide 71
Slide 71 text
class MainActivity : BaseActivity(),
SuperHeroesPresenter.View {
override val presenter: SuperHeroesPresenter by
injector.instance()
/*...*/
override val activityModules =
Module(allowSilentOverride = true) {
bind() with provider {
SuperHeroesPresenter(this@MainActivity,
instance())
}
bind() with provider{
GetSuperHeroes(instance()) }
}
Slide 72
Slide 72 text
class MainActivity : BaseActivity(),
SuperHeroesPresenter.View {
override val presenter: SuperHeroesPresenter by
injector.instance()
/*...*/
override val activityModules =
Module(allowSilentOverride = true) {
bind() with provider {
SuperHeroesPresenter(this@MainActivity,
instance())
}
bind() with provider{
GetSuperHeroes(instance()) }
}
Slide 73
Slide 73 text
class MainActivity : BaseActivity(),
SuperHeroesPresenter.View {
override val presenter: SuperHeroesPresenter by
injector.instance()
/*...*/
override val activityModules =
Module(allowSilentOverride = true) {
bind() with provider {
SuperHeroesPresenter(this@MainActivity,
instance())
}
bind() with provider{
GetSuperHeroes(instance()) }
}
Slide 74
Slide 74 text
Bibliography
They’re the cracks!
Clean Code. Uncle Bob. 2008
Kotlin For Android Developers. Antonio Leiva. 2015
GitHub AsyncAwait . metalabdesign.
Github funKTionale. MarioAriasC.
Kodein. Salomon Brys.
Karumi Blog and Katas on github.