SlideShare a Scribd company logo
SOLID principles in practice:
the clean architecture
Fabio Collini
@fabioCollini
linkedin.com/in/fabiocollini
github.com/fabioCollini
medium.com/@fabioCollini
codingjam.it
Android programmazione avanzata
Android Developers Italia
Ego slide
entitiesentities
Clean
architecture
S.O.L.I.D.
principles
class WeatherUseCase(
private val locationManager: LocationManager,
private val repository: TemperatureRepository) {
suspend fun getCityData(): String = coroutineScope {
try {
val location = locationManager.getLastLocation()
val cities = async { locationManager.getCities(location) }
val temperature = repository.getTemperature(
location.lat, location.lon)
val city = cities.await().getOrElse(0) { "No city found" }
"$city n $temperature"
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
Coroutines
class WeatherUseCase(
private val locationManager: LocationManager,
private val repository: TemperatureRepository) {
suspend fun getCityData(): String = coroutineScope {
try {
val location = locationManager.getLastLocation()
val cities = async { locationManager.getCities(location) }
val temperature = repository.getTemperature(
location.lat, location.lon)
val city = cities.await().getOrElse(0) { "No city found" }
"$city n $temperature"
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
Dependency Injection
class WeatherUseCase(
private val locationManager: LocationManager,
private val repository: TemperatureRepository) {
suspend fun getCityData(): String = coroutineScope {
try {
val location = locationManager.getLastLocation()
val cities = async { locationManager.getCities(location) }
val temperature = repository.getTemperature(
location.lat, location.lon)
val city = cities.await().getOrElse(0) { "No city found" }
"$city n $temperature"
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
class WeatherUseCaseTest {
val locationManager: LocationManager = mockk()
val repository: TemperatureRepository = mockk()
val useCase = WeatherUseCase(locationManager, repository)
@Test
fun retrieveCityData() {
coEvery { locationManager.getLastLocation() } returns LOCATION
coEvery { locationManager.getCities(LOCATION) } returns
listOf(City("Firenze", "IT"))
coEvery { repository.getTemperature(LAT, LON) } returns
Temperature(10, 8, 20)
val cityData = runBlocking { useCase.getCityData() }
assert(cityData).isEqualTo("Firenze (IT) n 10º min 8º max 20º")
}1
}2
interface LocationManager {
suspend fun getLastLocation(): Location
suspend fun getCities(location: Location): List<City>
}6
class WeatherUseCase(
private val locationManager: LocationManager,
private val repository: TemperatureRepository) {
suspend fun getCityData(): String = coroutineScope {
try {
val location = locationManager.getLastLocation()
val cities = async { locationManager.getCities(location) }
val temperature = repository.getTemperature(
location.lat, location.lon)
val city = cities.await().getOrElse(0) { "No city found" }
"$city n $temperature"
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
interface TemperatureRepository {
suspend fun getTemperature(lat: Double, lon: Double): Temperature
}7
interface LocationManager {
suspend fun getLastLocation(): Location
suspend fun getCities(location: Location): List<City>
}6
class AndroidLocationManager(context: Context) : LocationManager {
private val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private val geocoder = Geocoder(context, Locale.getDefault())
override suspend fun getLastLocation(): Location = suspendCoroutine {
//...
}E
override suspend fun getCities(location: Location): List<City> = suspendCoroutine {
//...
}E
}E
interface TemperatureRepository {
suspend fun getTemperature(lat: Double, lon: Double): Temperature
}7
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(lat: Double, lon: Double): Temperature {
val forecastDeferred = api.forecast(lat, lon)
val weather = api.currentWeather(lat, lon).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
class AndroidLocationManager(context: Context) : LocationManager {
private val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private val geocoder = Geocoder(context, Locale.getDefault())
override suspend fun getLastLocation(): Location = suspendCoroutine {
//...
}E
override suspend fun getCities(location: Location): List<City> = suspendCoroutine {
//...
}E
}E
class AndroidLocationManager(context: Context) : LocationManager {
private val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private val geocoder = Geocoder(context, Locale.getDefault())
@SuppressLint("MissingPermission")
override suspend fun getLastLocation(): Location = suspendCoroutine { continuation ->
fusedLocationClient.lastLocation
.addOnSuccessListener { location ->
if (location == null)
continuation.resumeWithException(Exception("Location not available"))
else
continuation.resume(Location(location.latitude, location.longitude))
}
.addOnFailureListener {
continuation.resumeWithException(it)
}
}E
override suspend fun getCities(location: Location): List<City> = suspendCoroutine { continuation ->
val addresses = geocoder.getFromLocation(location.lat, location.lon, 10)
continuation.resume(addresses
.filter { it.locality != null }
.map {
City(it.locality, it.countryCode)
}
)
}E
}E
interface LocationManager {
suspend fun getLastLocation(): Location
suspend fun getCities(location: Location): List<City>
}6
class AndroidLocationManager(context: Context) : LocationManager {
private val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private val geocoder = Geocoder(context, Locale.getDefault())
override suspend fun getLastLocation(): Location = suspendCoroutine {
//...
}E
override suspend fun getCities(location: Location): List<City> = suspendCoroutine {
//...
}E
}E
interface TemperatureRepository {
suspend fun getTemperature(lat: Double, lon: Double): Temperature
}7
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(lat: Double, lon: Double): Temperature {
val forecastDeferred = api.forecast(lat, lon)
val weather = api.currentWeather(lat, lon).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(lat: Double, lon: Double): Temperature {
val forecastDeferred = api.forecast(lat, lon)
val weather = api.currentWeather(lat, lon).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(lat: Double, lon: Double): Temperature {
val forecastDeferred = api.forecast(lat, lon)
val weather = api.currentWeather(lat, lon).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
interface LocationManager {
suspend fun getLastLocation(): Location
suspend fun getCities(location: Location): List<City>
}6
class AndroidLocationManager(context: Context) : LocationManager {
private val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private val geocoder = Geocoder(context, Locale.getDefault())
override suspend fun getLastLocation(): Location = suspendCoroutine {
//...
}E
override suspend fun getCities(location: Location): List<City> = suspendCoroutine {
//...
}E
}E
interface TemperatureRepository {
suspend fun getTemperature(lat: Double, lon: Double): Temperature
}7
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(lat: Double, lon: Double): Temperature {
val forecastDeferred = api.forecast(lat, lon)
val weather = api.currentWeather(lat, lon).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<Forecast>
}Z
interface LocationManager {
suspend fun getLastLocation(): Location
suspend fun getCities(location: Location): List<City>
}6
class AndroidLocationManager(context: Context) : LocationManager {
private val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private val geocoder = Geocoder(context, Locale.getDefault())
override suspend fun getLastLocation(): Location = suspendCoroutine {
//...
}E
override suspend fun getCities(location: Location): List<City> = suspendCoroutine {
//...
}E
}E
interface TemperatureRepository {
suspend fun getTemperature(lat: Double, lon: Double): Temperature
}7
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(lat: Double, lon: Double): Temperature {
val forecastDeferred = api.forecast(lat, lon)
val weather = api.currentWeather(lat, lon).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<Forecast>
}Z
interface TemperatureRepository {
suspend fun getTemperature(lat: Double, lon: Double): Temperature
}7
interface LocationManager {
suspend fun getLastLocation(): Location
suspend fun getCities(location: Location): List<City>
}6
class AndroidLocationManager(context: Context) : LocationManager {
private val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private val geocoder = Geocoder(context, Locale.getDefault())
override suspend fun getLastLocation(): Location = suspendCoroutine {
//...
}E
override suspend fun getCities(location: Location): List<City> = suspendCoroutine {
//...
}E
}E
class WeatherUseCase(
private val locationManager: LocationManager,
private val repository: TemperatureRepository) {
suspend fun getCityData(): String = coroutineScope {
try {
val location = locationManager.getLastLocation()
val cities = async { locationManager.getCities(location) }
val temperature = repository.getTemperature(
location.lat, location.lon)
val city = cities.await().getOrElse(0) { "No city found" }
"$city n $temperature"
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<Forecast>
}Z
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(lat: Double, lon: Double): Temperature {
val forecastDeferred = api.forecast(lat, lon)
val weather = api.currentWeather(lat, lon).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
class WeatherUseCase(
private val locationManager: LocationManager,
private val repository: TemperatureRepository) {
suspend fun getCityData(): String = coroutineScope {
try {
val location = locationManager.getLastLocation()
val cities = async { locationManager.getCities(location) }
val temperature = repository.getTemperature(
location.lat, location.lon)
val city = cities.await().getOrElse(0) { "No city found" }
"$city n $temperature"
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
class WeatherViewModel(app: Application) : AndroidViewModel(app) {
private val api = RetrofitFactory.createService<WeatherApi>()
private val weatherRepository = OpenWeatherTemperatureRepository(api)
private val positionManager = AndroidLocationManager(app)
private val useCase = WeatherUseCase(positionManager, weatherRepository)
val state = MutableLiveData<String>()
fun load() {
viewModelScope.launch {
val result = useCase.getCityData()
state.value = result
}O
}O
}O
class MainActivity : AppCompatActivity() {
//...
}
interface LocationManager {
suspend fun getLastLocation(): Location
suspend fun getCities(location: Location): List<City>
}6
class AndroidLocationManager(context: Context) : LocationManager {
private val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private val geocoder = Geocoder(context, Locale.getDefault())
override suspend fun getLastLocation(): Location = suspendCoroutine {
//...
}E
override suspend fun getCities(location: Location): List<City> = suspendCoroutine {
//...
}E
}E
interface TemperatureRepository {
suspend fun getTemperature(lat: Double, lon: Double): Temperature
}7
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<Forecast>
}Z
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(lat: Double, lon: Double): Temperature {
val forecastDeferred = api.forecast(lat, lon)
val weather = api.currentWeather(lat, lon).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
class WeatherViewModel(app: Application) : AndroidViewModel(app) {
private val api = RetrofitFactory.createService<WeatherApi>()
private val weatherRepository = OpenWeatherTemperatureRepository(api)
private val positionManager = AndroidLocationManager(app)
private val useCase = WeatherUseCase(positionManager, weatherRepository)
val state = MutableLiveData<String>()
fun load() {
viewModelScope.launch {
val result = useCase.getCityData()
state.value = result
}O
}O
}O
app
api
domain
weather
location
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<Forecast>
}Z
class WeatherUseCase(
private val locationManager: LocationManager,
private val repository: TemperatureRepository) {
suspend fun getCityData(): String = coroutineScope {
try {
val location = locationManager.getLastLocation()
val cities = async { locationManager.getCities(location) }
val temperature = repository.getTemperature(
location.lat, location.lon)
val city = cities.await().getOrElse(0) { "No city found" }
"$city n $temperature"
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
class MainActivity : AppCompatActivity() {
//...
}
interface LocationManager {
suspend fun getLastLocation(): Location
suspend fun getCities(location: Location): List<City>
}6
class AndroidLocationManager(context: Context) : LocationManager {
private val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private val geocoder = Geocoder(context, Locale.getDefault())
override suspend fun getLastLocation(): Location = suspendCoroutine {
//...
}E
override suspend fun getCities(location: Location): List<City> = suspendCoroutine {
//...
}E
}E
interface TemperatureRepository {
suspend fun getTemperature(lat: Double, lon: Double): Temperature
}7
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(lat: Double, lon: Double): Temperature {
val forecastDeferred = api.forecast(lat, lon)
val weather = api.currentWeather(lat, lon).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
class WeatherViewModel(app: Application) : AndroidViewModel(app) {
private val api = RetrofitFactory.createService<WeatherApi>()
private val weatherRepository = OpenWeatherTemperatureRepository(api)
private val positionManager = AndroidLocationManager(app)
private val useCase = WeatherUseCase(positionManager, weatherRepository)
val state = MutableLiveData<String>()
fun load() {
viewModelScope.launch {
val result = useCase.getCityData()
state.value = result
}O
}O
}O
Java/Kotlin modules are more…
Testable
Reusable
Robert C. Martin Copyright (c) 2000 by Robert C. Martin. All Rights Reserved.
www.objectmentor.com 1
Design Principles and
Design Patterns
Robert C. Martin
www.objectmentor.com
What is software architecture? The answer is multitiered. At the highest level, there
are the architecture patterns that define the overall shape and structure of software
applications1
. Down a level is the architecture that is specifically related to the pur-
pose of the software application. Yet another level down resides the architecture of
the modules and their interconnections. This is the domain of design patterns2
, pack-
akges, components, and classes. It is this level that we will concern ourselves with in
this chapter.
Our scope in this chapter is quite limitted. There is much more to be said about the
principles and patterns that are exposed here. Interested readers are referred to
[Martin99].
Architecture and Dependencies
What goes wrong with software? The design of many software applications begins as
a vital image in the minds of its designers. At this stage it is clean, elegant, and com-
pelling. It has a simple beauty that makes the designers and implementers itch to see it
working. Some of these applications manage to maintain this purity of design through
the initial development and into the first release.
But then something begins to happen. The software starts to rot. At first it isn’t so
bad. An ugly wart here, a clumsy hack there, but the beauty of the design still shows
through. Yet, over time as the rotting continues, the ugly festering sores and boils
accumulate until they dominate the design of the application. The program becomes a
festering mass of code that the developers find increasingly hard to maintain. Eventu-
1. [Shaw96]
2. [GOF96]
Copyright (c) 2000 by Robert C. Martin. All Rights Reserved.
S
O
L
I
D
Single responsibility
“A class should have one, and only one,
reason to change”
app
domain
api
weather
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<Forecast>
}Z
interface TemperatureRepository {
suspend fun getTemperature(lat: Double, lon: Double): Temperature
}7
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(lat: Double, lon: Double): Temperature {
val forecastDeferred = api.forecast(lat, lon)
val weather = api.currentWeather(lat, lon).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
class WeatherUseCase(
private val locationManager: LocationManager,
private val repository: TemperatureRepository) {
suspend fun getCityData(): String = coroutineScope {
try {
val location = locationManager.getLastLocation()
val cities = async { locationManager.getCities(location) }
val temperature = repository.getTemperature(
location.lat, location.lon)
val city = cities.await().getOrElse(0) { "No city found" }
"$city n $temperature"
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
class WeatherViewModel(app: Application) : AndroidViewModel(app) {
private val api = RetrofitFactory.createService<WeatherApi>()
private val weatherRepository = OpenWeatherTemperatureRepository(api)
private val positionManager = AndroidLocationManager(app)
private val useCase = WeatherUseCase(positionManager, weatherRepository)
val state = MutableLiveData<String>()
fun load() {
viewModelScope.launch {
val result = useCase.getCityData()
state.value = result
}O
}O
}O
class MainActivity : AppCompatActivity() {
//...
}
location
interface LocationManager {
suspend fun getLastLocation(): Location
suspend fun getCities(location: Location): List<City>
}6
class AndroidLocationManager(context: Context) : LocationManager {
private val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private val geocoder = Geocoder(context, Locale.getDefault())
override suspend fun getLastLocation(): Location = suspendCoroutine {
//...
}E
override suspend fun getCities(location: Location): List<City> = suspendCoroutine {
//...
}E
}E
S
O
L
I
D
Open closed
“You should be able to extend a classes
behavior, without modifying it”
interface
impl1 impl2
interface
impl1 impl2 impl3
S
O
L
I
D
Liskov substitution
“Derived classes must be substitutable
for their base classes”
open class Rectangle(
open var width: Int,
open var height: Int
) {
fun area() = width * height
}
class Square(size: Int) : Rectangle(size, size) {
override var width: Int = size
set(value) {
field = value
if (height != value)
height = value
}
override var height: Int = size
set(value) {
field = value
if (width != value)
width = value
}
}
var rectangle = Rectangle(2, 3)
var initialArea = rectangle.area()
rectangle.width *= 2
println(initialArea * 2 == rectangle.area())
rectangle = Square(2)
initialArea = rectangle.area()
rectangle.width *= 2
println(initialArea * 2 == rectangle.area())
S
O
L
I
D
Interface segregation
“Make fine grained interfaces
that are client specific”
S
O
L
I
D
Dependency inversion
Dependency injection?
Inversion of control?
Dependency inversion
“Depend on abstractions,
not on concretions”
app
domain
api
weather
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<Forecast>
}Z
interface TemperatureRepository {
suspend fun getTemperature(lat: Double, lon: Double): Temperature
}7
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(lat: Double, lon: Double): Temperature {
val forecastDeferred = api.forecast(lat, lon)
val weather = api.currentWeather(lat, lon).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
class WeatherUseCase(
private val locationManager: LocationManager,
private val repository: TemperatureRepository) {
suspend fun getCityData(): String = coroutineScope {
try {
val location = locationManager.getLastLocation()
val cities = async { locationManager.getCities(location) }
val temperature = repository.getTemperature(
location.lat, location.lon)
val city = cities.await().getOrElse(0) { "No city found" }
"$city n $temperature"
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
class WeatherViewModel(app: Application) : AndroidViewModel(app) {
private val api = RetrofitFactory.createService<WeatherApi>()
private val weatherRepository = OpenWeatherTemperatureRepository(api)
private val positionManager = AndroidLocationManager(app)
private val useCase = WeatherUseCase(positionManager, weatherRepository)
val state = MutableLiveData<String>()
fun load() {
viewModelScope.launch {
val result = useCase.getCityData()
state.value = result
}O
}O
}O
class MainActivity : AppCompatActivity() {
//...
}
location
interface LocationManager {
suspend fun getLastLocation(): Location
suspend fun getCities(location: Location): List<City>
}6
class AndroidLocationManager(context: Context) : LocationManager {
private val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private val geocoder = Geocoder(context, Locale.getDefault())
override suspend fun getLastLocation(): Location = suspendCoroutine {
//...
}E
override suspend fun getCities(location: Location): List<City> = suspendCoroutine {
//...
}E
}E
Dependency inversion
“Depend on abstractions,
not on concretions”
Dependency inversion
“High level modules should not depend
upon low level modules. Both should
depend upon abstractions”
app
domain
api
weather
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<Forecast>
}Z
interface TemperatureRepository {
suspend fun getTemperature(lat: Double, lon: Double): Temperature
}7
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(lat: Double, lon: Double): Temperature {
val forecastDeferred = api.forecast(lat, lon)
val weather = api.currentWeather(lat, lon).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
class WeatherUseCase(
private val locationManager: LocationManager,
private val repository: TemperatureRepository) {
suspend fun getCityData(): String = coroutineScope {
try {
val location = locationManager.getLastLocation()
val cities = async { locationManager.getCities(location) }
val temperature = repository.getTemperature(
location.lat, location.lon)
val city = cities.await().getOrElse(0) { "No city found" }
"$city n $temperature"
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
class WeatherViewModel(app: Application) : AndroidViewModel(app) {
private val api = RetrofitFactory.createService<WeatherApi>()
private val weatherRepository = OpenWeatherTemperatureRepository(api)
private val positionManager = AndroidLocationManager(app)
private val useCase = WeatherUseCase(positionManager, weatherRepository)
val state = MutableLiveData<String>()
fun load() {
viewModelScope.launch {
val result = useCase.getCityData()
state.value = result
}O
}O
}O
class MainActivity : AppCompatActivity() {
//...
}
location
interface LocationManager {
suspend fun getLastLocation(): Location
suspend fun getCities(location: Location): List<City>
}6
class AndroidLocationManager(context: Context) : LocationManager {
private val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private val geocoder = Geocoder(context, Locale.getDefault())
override suspend fun getLastLocation(): Location = suspendCoroutine {
//...
}E
override suspend fun getCities(location: Location): List<City> = suspendCoroutine {
//...
}E
}E
domain location
interface LocationManager {
suspend fun getLastLocation(): Location
suspend fun getCities(location: Location): List<City>
}6
class AndroidLocationManager(context: Context) : LocationManager {
private val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private val geocoder = Geocoder(context, Locale.getDefault())
override suspend fun getLastLocation(): Location = suspendCoroutine {
//...
}E
override suspend fun getCities(location: Location): List<City> = suspendCoroutine {
//...
}E
}E
class WeatherUseCase(
private val locationManager: LocationManager,
private val repository: TemperatureRepository) {
suspend fun getCityData(): String = coroutineScope {
try {
val location = locationManager.getLastLocation()
val cities = async { locationManager.getCities(location) }
val temperature = repository.getTemperature(
location.lat, location.lon)
val city = cities.await().getOrElse(0) { "No city found" }
"$city n $temperature"
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
domain location
class WeatherUseCase(
private val locationManager: LocationManager,
private val repository: TemperatureRepository) {
suspend fun getCityData(): String = coroutineScope {
try {
val location = locationManager.getLastLocation()
val cities = async { locationManager.getCities(location) }
val temperature = repository.getTemperature(
location.lat, location.lon)
val city = cities.await().getOrElse(0) { "No city found" }
"$city n $temperature"
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
interface LocationManager {
suspend fun getLastLocation(): Location
suspend fun getCities(location: Location): List<City>
}6
class AndroidLocationManager(context: Context) : LocationManager {
private val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private val geocoder = Geocoder(context, Locale.getDefault())
override suspend fun getLastLocation(): Location = suspendCoroutine {
//...
}E
override suspend fun getCities(location: Location): List<City> = suspendCoroutine {
//...
}E
}E
app
domain
api
weather
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double):
Deferred<Forecast>
}Z
interface TemperatureRepository {
suspend fun getTemperature(lat: Double, lon: Double): Temperature
}7
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(lat: Double, lon: Double): Temperature {
val forecastDeferred = api.forecast(lat, lon)
val weather = api.currentWeather(lat, lon).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
location
class WeatherUseCase(
private val locationManager: LocationManager,
private val repository: TemperatureRepository) {
suspend fun getCityData(): String = coroutineScope {
try {
val location = locationManager.getLastLocation()
val cities = async { locationManager.getCities(location) }
val temperature = repository.getTemperature(
location.lat, location.lon)
val city = cities.await().getOrElse(0) { "No city found" }
"$city n $temperature"
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
interface LocationManager {
suspend fun getLastLocation(): Location
suspend fun getCities(location: Location): List<City>
}6
class AndroidLocationManager(context: Context) : LocationManager {
private val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private val geocoder = Geocoder(context, Locale.getDefault())
override suspend fun getLastLocation(): Location = suspendCoroutine {
//...
}E
override suspend fun getCities(location: Location): List<City> = suspendCoroutine {
//...
}E
}E
class WeatherViewModel(app: Application) : AndroidViewModel(app) {
private val api = RetrofitFactory.createService<WeatherApi>()
private val weatherRepository = OpenWeatherTemperatureRepository(api)
private val positionManager = AndroidLocationManager(app)
private val useCase = WeatherUseCase(positionManager, weatherRepository)
val state = MutableLiveData<String>()
fun load() {
viewModelScope.launch {
val result = useCase.getCityData()
state.value = result
}O
}O
}O
class MainActivity : AppCompatActivity() {
//...
}
location
entities
app
weather
api
domain
location
entities
app
weather
api
domain
location
entities
app
weather
api
domain
location
entities
app
weather
api
domain
location
entities
app
weather
api
domain
location
entities
app
weather
api
repository
entities
UI
data source
domain
domain
entitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
repository
entities
UI
data source
domain
entitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
Module by feature
OR
Module by layer
entitiesentities
entities
1
domain1
apiRepository
feature1
presenter1
ap1
entities
2
entities
3
featureN
presenterN
…
…
dbRepository
db
ap2
domain2
Prosentitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
Framework
independence
entitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
Architecture
Vs
code conventions
entitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
TDDentitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
Consentitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
More codeentitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
Architecture Vs
code conventions
TDD
Pros
Cons
More code
Framework
independence
entitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
Links
Demo Project
github.com/fabioCollini/CleanWeather
2000 - Robert C. Martin - Design Principles and Design Patterns
www.cvc.uab.es/shared/teach/a21291/temes/object_oriented_design/
materials_adicionals/principles_and_patterns.pdf
2005 - Robert C. Martin - The Principles of OOD
butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
2012 - Robert C. Martin - The Clean Architecture
8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
THANKS
FOR YOUR
ATTENTION
QUESTIONS?
androiddevs.it

More Related Content

What's hot

Dpilot Source Code With ScreenShots
Dpilot Source Code With ScreenShots Dpilot Source Code With ScreenShots
Dpilot Source Code With ScreenShots
DeepAnshu Sharma
 
The Ring programming language version 1.9 book - Part 90 of 210
The Ring programming language version 1.9 book - Part 90 of 210The Ring programming language version 1.9 book - Part 90 of 210
The Ring programming language version 1.9 book - Part 90 of 210
Mahmoud Samir Fayed
 
Higher Order Components and Render Props
Higher Order Components and Render PropsHigher Order Components and Render Props
Higher Order Components and Render Props
Nitish Phanse
 
Developing Applications with Microsoft Virtual Earth
Developing Applications with Microsoft Virtual EarthDeveloping Applications with Microsoft Virtual Earth
Developing Applications with Microsoft Virtual Earth
goodfriday
 
Angular and The Case for RxJS
Angular and The Case for RxJSAngular and The Case for RxJS
Angular and The Case for RxJS
Sandi Barr
 
Introductory RxJava
Introductory RxJavaIntroductory RxJava
Introductory RxJava
Intae Kim
 
JavaScript Event Loop
JavaScript Event LoopJavaScript Event Loop
JavaScript Event Loop
Designveloper
 
JavaScript Event Loop
JavaScript Event LoopJavaScript Event Loop
JavaScript Event Loop
Derek Willian Stavis
 
Functional Stream Processing with Scalaz-Stream
Functional Stream Processing with Scalaz-StreamFunctional Stream Processing with Scalaz-Stream
Functional Stream Processing with Scalaz-Stream
Adil Akhter
 
Swift で数学のススメ 〜 プログラミングと数学は同時に学べ
Swift で数学のススメ 〜 プログラミングと数学は同時に学べSwift で数学のススメ 〜 プログラミングと数学は同時に学べ
Swift で数学のススメ 〜 プログラミングと数学は同時に学べ
Taketo Sano
 
視覚化とSwiftのタイプについて
視覚化とSwiftのタイプについて視覚化とSwiftのタイプについて
視覚化とSwiftのタイプについて
Ray Fix
 
FS2 for Fun and Profit
FS2 for Fun and ProfitFS2 for Fun and Profit
FS2 for Fun and Profit
Adil Akhter
 
Intro to Reactive Thinking and RxJava 2
Intro to Reactive Thinking and RxJava 2Intro to Reactive Thinking and RxJava 2
Intro to Reactive Thinking and RxJava 2
JollyRogers5
 
The Ring programming language version 1.3 book - Part 25 of 88
The Ring programming language version 1.3 book - Part 25 of 88The Ring programming language version 1.3 book - Part 25 of 88
The Ring programming language version 1.3 book - Part 25 of 88
Mahmoud Samir Fayed
 
Quest 1 define a class batsman with the following specifications
Quest  1 define a class batsman with the following specificationsQuest  1 define a class batsman with the following specifications
Quest 1 define a class batsman with the following specifications
rajkumari873
 
20070329 Java Programing Tips
20070329 Java Programing Tips20070329 Java Programing Tips
20070329 Java Programing Tips
Shingo Furuyama
 
Promises in iOS development
Promises in iOS developmentPromises in iOS development
Promises in iOS development
RomanPanichkin
 
Think Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSThink Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJS
Adam L Barrett
 
The Ring programming language version 1.5.2 book - Part 75 of 181
The Ring programming language version 1.5.2 book - Part 75 of 181The Ring programming language version 1.5.2 book - Part 75 of 181
The Ring programming language version 1.5.2 book - Part 75 of 181
Mahmoud Samir Fayed
 
Reactive Programming with RxSwift
Reactive Programming with RxSwiftReactive Programming with RxSwift
Reactive Programming with RxSwift
Scott Gardner
 

What's hot (20)

Dpilot Source Code With ScreenShots
Dpilot Source Code With ScreenShots Dpilot Source Code With ScreenShots
Dpilot Source Code With ScreenShots
 
The Ring programming language version 1.9 book - Part 90 of 210
The Ring programming language version 1.9 book - Part 90 of 210The Ring programming language version 1.9 book - Part 90 of 210
The Ring programming language version 1.9 book - Part 90 of 210
 
Higher Order Components and Render Props
Higher Order Components and Render PropsHigher Order Components and Render Props
Higher Order Components and Render Props
 
Developing Applications with Microsoft Virtual Earth
Developing Applications with Microsoft Virtual EarthDeveloping Applications with Microsoft Virtual Earth
Developing Applications with Microsoft Virtual Earth
 
Angular and The Case for RxJS
Angular and The Case for RxJSAngular and The Case for RxJS
Angular and The Case for RxJS
 
Introductory RxJava
Introductory RxJavaIntroductory RxJava
Introductory RxJava
 
JavaScript Event Loop
JavaScript Event LoopJavaScript Event Loop
JavaScript Event Loop
 
JavaScript Event Loop
JavaScript Event LoopJavaScript Event Loop
JavaScript Event Loop
 
Functional Stream Processing with Scalaz-Stream
Functional Stream Processing with Scalaz-StreamFunctional Stream Processing with Scalaz-Stream
Functional Stream Processing with Scalaz-Stream
 
Swift で数学のススメ 〜 プログラミングと数学は同時に学べ
Swift で数学のススメ 〜 プログラミングと数学は同時に学べSwift で数学のススメ 〜 プログラミングと数学は同時に学べ
Swift で数学のススメ 〜 プログラミングと数学は同時に学べ
 
視覚化とSwiftのタイプについて
視覚化とSwiftのタイプについて視覚化とSwiftのタイプについて
視覚化とSwiftのタイプについて
 
FS2 for Fun and Profit
FS2 for Fun and ProfitFS2 for Fun and Profit
FS2 for Fun and Profit
 
Intro to Reactive Thinking and RxJava 2
Intro to Reactive Thinking and RxJava 2Intro to Reactive Thinking and RxJava 2
Intro to Reactive Thinking and RxJava 2
 
The Ring programming language version 1.3 book - Part 25 of 88
The Ring programming language version 1.3 book - Part 25 of 88The Ring programming language version 1.3 book - Part 25 of 88
The Ring programming language version 1.3 book - Part 25 of 88
 
Quest 1 define a class batsman with the following specifications
Quest  1 define a class batsman with the following specificationsQuest  1 define a class batsman with the following specifications
Quest 1 define a class batsman with the following specifications
 
20070329 Java Programing Tips
20070329 Java Programing Tips20070329 Java Programing Tips
20070329 Java Programing Tips
 
Promises in iOS development
Promises in iOS developmentPromises in iOS development
Promises in iOS development
 
Think Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSThink Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJS
 
The Ring programming language version 1.5.2 book - Part 75 of 181
The Ring programming language version 1.5.2 book - Part 75 of 181The Ring programming language version 1.5.2 book - Part 75 of 181
The Ring programming language version 1.5.2 book - Part 75 of 181
 
Reactive Programming with RxSwift
Reactive Programming with RxSwiftReactive Programming with RxSwift
Reactive Programming with RxSwift
 

Similar to SOLID principles in practice: the Clean Architecture - Devfest Emila Romagna

Solid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon ItalySolid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon Italy
Fabio Collini
 
Managing parallelism using coroutines
Managing parallelism using coroutinesManaging parallelism using coroutines
Managing parallelism using coroutines
Fabio Collini
 
K is for Kotlin
K is for KotlinK is for Kotlin
K is for Kotlin
TechMagic
 
Java VS Python
Java VS PythonJava VS Python
Java VS Python
Simone Federici
 
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
Codemotion
 
FalsyValues. Dmitry Soshnikov - ECMAScript 6
FalsyValues. Dmitry Soshnikov - ECMAScript 6FalsyValues. Dmitry Soshnikov - ECMAScript 6
FalsyValues. Dmitry Soshnikov - ECMAScript 6
Dmitry Soshnikov
 
Web2Day 2017 - Concilier DomainDriveDesign et API REST
Web2Day 2017 - Concilier DomainDriveDesign et API RESTWeb2Day 2017 - Concilier DomainDriveDesign et API REST
Web2Day 2017 - Concilier DomainDriveDesign et API REST
Nicolas Faugout
 
Migrating from Flux to Redux. Why and how.
Migrating from Flux to Redux. Why and how.Migrating from Flux to Redux. Why and how.
Migrating from Flux to Redux. Why and how.
Astrails
 
Using the Windows 8 Runtime from C++
Using the Windows 8 Runtime from C++Using the Windows 8 Runtime from C++
Architecting Alive Apps
Architecting Alive AppsArchitecting Alive Apps
Architecting Alive Apps
Jorge Ortiz
 
Taming Core Data by Arek Holko, Macoscope
Taming Core Data by Arek Holko, MacoscopeTaming Core Data by Arek Holko, Macoscope
Taming Core Data by Arek Holko, Macoscope
Macoscope
 
RxJava и Android. Плюсы, минусы, подводные камни
RxJava и Android. Плюсы, минусы, подводные камниRxJava и Android. Плюсы, минусы, подводные камни
RxJava и Android. Плюсы, минусы, подводные камни
Stfalcon Meetups
 
Anti patterns
Anti patternsAnti patterns
Anti patterns
Alex Tumanoff
 
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf MilanFrom Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
Fabio Collini
 
F# Presentation for SmartDevs, Hereford
F# Presentation for SmartDevs, HerefordF# Presentation for SmartDevs, Hereford
F# Presentation for SmartDevs, Hereford
Kit Eason
 
Introduction to kotlin
Introduction to kotlinIntroduction to kotlin
Introduction to kotlin
Shaul Rosenzwieg
 
Average- An android project
Average- An android projectAverage- An android project
Average- An android project
Ipsit Dash
 
Deep Dumpster Diving
Deep Dumpster DivingDeep Dumpster Diving
Deep Dumpster Diving
RonnBlack
 
Async JavaScript in ES7
Async JavaScript in ES7Async JavaScript in ES7
Async JavaScript in ES7
Mike North
 
Retrofit
RetrofitRetrofit
Retrofit
bresiu
 

Similar to SOLID principles in practice: the Clean Architecture - Devfest Emila Romagna (20)

Solid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon ItalySolid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon Italy
 
Managing parallelism using coroutines
Managing parallelism using coroutinesManaging parallelism using coroutines
Managing parallelism using coroutines
 
K is for Kotlin
K is for KotlinK is for Kotlin
K is for Kotlin
 
Java VS Python
Java VS PythonJava VS Python
Java VS Python
 
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
 
FalsyValues. Dmitry Soshnikov - ECMAScript 6
FalsyValues. Dmitry Soshnikov - ECMAScript 6FalsyValues. Dmitry Soshnikov - ECMAScript 6
FalsyValues. Dmitry Soshnikov - ECMAScript 6
 
Web2Day 2017 - Concilier DomainDriveDesign et API REST
Web2Day 2017 - Concilier DomainDriveDesign et API RESTWeb2Day 2017 - Concilier DomainDriveDesign et API REST
Web2Day 2017 - Concilier DomainDriveDesign et API REST
 
Migrating from Flux to Redux. Why and how.
Migrating from Flux to Redux. Why and how.Migrating from Flux to Redux. Why and how.
Migrating from Flux to Redux. Why and how.
 
Using the Windows 8 Runtime from C++
Using the Windows 8 Runtime from C++Using the Windows 8 Runtime from C++
Using the Windows 8 Runtime from C++
 
Architecting Alive Apps
Architecting Alive AppsArchitecting Alive Apps
Architecting Alive Apps
 
Taming Core Data by Arek Holko, Macoscope
Taming Core Data by Arek Holko, MacoscopeTaming Core Data by Arek Holko, Macoscope
Taming Core Data by Arek Holko, Macoscope
 
RxJava и Android. Плюсы, минусы, подводные камни
RxJava и Android. Плюсы, минусы, подводные камниRxJava и Android. Плюсы, минусы, подводные камни
RxJava и Android. Плюсы, минусы, подводные камни
 
Anti patterns
Anti patternsAnti patterns
Anti patterns
 
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf MilanFrom Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
 
F# Presentation for SmartDevs, Hereford
F# Presentation for SmartDevs, HerefordF# Presentation for SmartDevs, Hereford
F# Presentation for SmartDevs, Hereford
 
Introduction to kotlin
Introduction to kotlinIntroduction to kotlin
Introduction to kotlin
 
Average- An android project
Average- An android projectAverage- An android project
Average- An android project
 
Deep Dumpster Diving
Deep Dumpster DivingDeep Dumpster Diving
Deep Dumpster Diving
 
Async JavaScript in ES7
Async JavaScript in ES7Async JavaScript in ES7
Async JavaScript in ES7
 
Retrofit
RetrofitRetrofit
Retrofit
 

More from Fabio Collini

Architectures in the compose world
Architectures in the compose worldArchitectures in the compose world
Architectures in the compose world
Fabio Collini
 
Using hilt in a modularized project
Using hilt in a modularized projectUsing hilt in a modularized project
Using hilt in a modularized project
Fabio Collini
 
Kotlin Delegates in practice - Kotlin community conf
Kotlin Delegates in practice - Kotlin community confKotlin Delegates in practice - Kotlin community conf
Kotlin Delegates in practice - Kotlin community conf
Fabio Collini
 
Kotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere StockholmKotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere Stockholm
Fabio Collini
 
Using Dagger in a Clean Architecture project
Using Dagger in a Clean Architecture projectUsing Dagger in a Clean Architecture project
Using Dagger in a Clean Architecture project
Fabio Collini
 
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night TurinAsync code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Fabio Collini
 
Recap Google I/O 2018
Recap Google I/O 2018Recap Google I/O 2018
Recap Google I/O 2018
Fabio Collini
 
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italyFrom java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
Fabio Collini
 
From java to kotlin beyond alt+shift+cmd+k
From java to kotlin beyond alt+shift+cmd+kFrom java to kotlin beyond alt+shift+cmd+k
From java to kotlin beyond alt+shift+cmd+k
Fabio Collini
 
Testing Android apps based on Dagger and RxJava Droidcon UK
Testing Android apps based on Dagger and RxJava Droidcon UKTesting Android apps based on Dagger and RxJava Droidcon UK
Testing Android apps based on Dagger and RxJava Droidcon UK
Fabio Collini
 
Intro to Retrofit 2 and RxJava2
Intro to Retrofit 2 and RxJava2Intro to Retrofit 2 and RxJava2
Intro to Retrofit 2 and RxJava2
Fabio Collini
 
Testing Android apps based on Dagger and RxJava
Testing Android apps based on Dagger and RxJavaTesting Android apps based on Dagger and RxJava
Testing Android apps based on Dagger and RxJava
Fabio Collini
 
Android Data Binding in action using MVVM pattern - droidconUK
Android Data Binding in action using MVVM pattern - droidconUKAndroid Data Binding in action using MVVM pattern - droidconUK
Android Data Binding in action using MVVM pattern - droidconUK
Fabio Collini
 
Data Binding in Action using MVVM pattern
Data Binding in Action using MVVM patternData Binding in Action using MVVM pattern
Data Binding in Action using MVVM pattern
Fabio Collini
 
Android Wear CodeLab - GDG Firenze
Android Wear CodeLab - GDG FirenzeAndroid Wear CodeLab - GDG Firenze
Android Wear CodeLab - GDG Firenze
Fabio Collini
 
Testable Android Apps using data binding and MVVM
Testable Android Apps using data binding and MVVMTestable Android Apps using data binding and MVVM
Testable Android Apps using data binding and MVVM
Fabio Collini
 
Introduction to Retrofit and RxJava
Introduction to Retrofit and RxJavaIntroduction to Retrofit and RxJava
Introduction to Retrofit and RxJava
Fabio Collini
 
Testable Android Apps DroidCon Italy 2015
Testable Android Apps DroidCon Italy 2015Testable Android Apps DroidCon Italy 2015
Testable Android Apps DroidCon Italy 2015
Fabio Collini
 
Clean android code - Droidcon Italiy 2014
Clean android code - Droidcon Italiy 2014Clean android code - Droidcon Italiy 2014
Clean android code - Droidcon Italiy 2014
Fabio Collini
 
Librerie su Android: come non reinventare la ruota @ whymca 2012
Librerie su Android: come non reinventare la ruota @ whymca 2012 Librerie su Android: come non reinventare la ruota @ whymca 2012
Librerie su Android: come non reinventare la ruota @ whymca 2012
Fabio Collini
 

More from Fabio Collini (20)

Architectures in the compose world
Architectures in the compose worldArchitectures in the compose world
Architectures in the compose world
 
Using hilt in a modularized project
Using hilt in a modularized projectUsing hilt in a modularized project
Using hilt in a modularized project
 
Kotlin Delegates in practice - Kotlin community conf
Kotlin Delegates in practice - Kotlin community confKotlin Delegates in practice - Kotlin community conf
Kotlin Delegates in practice - Kotlin community conf
 
Kotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere StockholmKotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere Stockholm
 
Using Dagger in a Clean Architecture project
Using Dagger in a Clean Architecture projectUsing Dagger in a Clean Architecture project
Using Dagger in a Clean Architecture project
 
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night TurinAsync code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
 
Recap Google I/O 2018
Recap Google I/O 2018Recap Google I/O 2018
Recap Google I/O 2018
 
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italyFrom java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
 
From java to kotlin beyond alt+shift+cmd+k
From java to kotlin beyond alt+shift+cmd+kFrom java to kotlin beyond alt+shift+cmd+k
From java to kotlin beyond alt+shift+cmd+k
 
Testing Android apps based on Dagger and RxJava Droidcon UK
Testing Android apps based on Dagger and RxJava Droidcon UKTesting Android apps based on Dagger and RxJava Droidcon UK
Testing Android apps based on Dagger and RxJava Droidcon UK
 
Intro to Retrofit 2 and RxJava2
Intro to Retrofit 2 and RxJava2Intro to Retrofit 2 and RxJava2
Intro to Retrofit 2 and RxJava2
 
Testing Android apps based on Dagger and RxJava
Testing Android apps based on Dagger and RxJavaTesting Android apps based on Dagger and RxJava
Testing Android apps based on Dagger and RxJava
 
Android Data Binding in action using MVVM pattern - droidconUK
Android Data Binding in action using MVVM pattern - droidconUKAndroid Data Binding in action using MVVM pattern - droidconUK
Android Data Binding in action using MVVM pattern - droidconUK
 
Data Binding in Action using MVVM pattern
Data Binding in Action using MVVM patternData Binding in Action using MVVM pattern
Data Binding in Action using MVVM pattern
 
Android Wear CodeLab - GDG Firenze
Android Wear CodeLab - GDG FirenzeAndroid Wear CodeLab - GDG Firenze
Android Wear CodeLab - GDG Firenze
 
Testable Android Apps using data binding and MVVM
Testable Android Apps using data binding and MVVMTestable Android Apps using data binding and MVVM
Testable Android Apps using data binding and MVVM
 
Introduction to Retrofit and RxJava
Introduction to Retrofit and RxJavaIntroduction to Retrofit and RxJava
Introduction to Retrofit and RxJava
 
Testable Android Apps DroidCon Italy 2015
Testable Android Apps DroidCon Italy 2015Testable Android Apps DroidCon Italy 2015
Testable Android Apps DroidCon Italy 2015
 
Clean android code - Droidcon Italiy 2014
Clean android code - Droidcon Italiy 2014Clean android code - Droidcon Italiy 2014
Clean android code - Droidcon Italiy 2014
 
Librerie su Android: come non reinventare la ruota @ whymca 2012
Librerie su Android: come non reinventare la ruota @ whymca 2012 Librerie su Android: come non reinventare la ruota @ whymca 2012
Librerie su Android: come non reinventare la ruota @ whymca 2012
 

Recently uploaded

Top 9 Trends in Cybersecurity for 2024.pptx
Top 9 Trends in Cybersecurity for 2024.pptxTop 9 Trends in Cybersecurity for 2024.pptx
Top 9 Trends in Cybersecurity for 2024.pptx
devvsandy
 
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdfTop Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
VALiNTRY360
 
zOS Mainframe JES2-JES3 JCL-JECL Differences
zOS Mainframe JES2-JES3 JCL-JECL DifferenceszOS Mainframe JES2-JES3 JCL-JECL Differences
zOS Mainframe JES2-JES3 JCL-JECL Differences
YousufSait3
 
Fundamentals of Programming and Language Processors
Fundamentals of Programming and Language ProcessorsFundamentals of Programming and Language Processors
Fundamentals of Programming and Language Processors
Rakesh Kumar R
 
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
dakas1
 
How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?
ToXSL Technologies
 
E-commerce Development Services- Hornet Dynamics
E-commerce Development Services- Hornet DynamicsE-commerce Development Services- Hornet Dynamics
E-commerce Development Services- Hornet Dynamics
Hornet Dynamics
 
Measures in SQL (SIGMOD 2024, Santiago, Chile)
Measures in SQL (SIGMOD 2024, Santiago, Chile)Measures in SQL (SIGMOD 2024, Santiago, Chile)
Measures in SQL (SIGMOD 2024, Santiago, Chile)
Julian Hyde
 
Artificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension FunctionsArtificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension Functions
Octavian Nadolu
 
Microservice Teams - How the cloud changes the way we work
Microservice Teams - How the cloud changes the way we workMicroservice Teams - How the cloud changes the way we work
Microservice Teams - How the cloud changes the way we work
Sven Peters
 
Everything You Need to Know About X-Sign: The eSign Functionality of XfilesPr...
Everything You Need to Know About X-Sign: The eSign Functionality of XfilesPr...Everything You Need to Know About X-Sign: The eSign Functionality of XfilesPr...
Everything You Need to Know About X-Sign: The eSign Functionality of XfilesPr...
XfilesPro
 
Energy consumption of Database Management - Florina Jonuzi
Energy consumption of Database Management - Florina JonuziEnergy consumption of Database Management - Florina Jonuzi
Energy consumption of Database Management - Florina Jonuzi
Green Software Development
 
Oracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptxOracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptx
Remote DBA Services
 
How to write a program in any programming language
How to write a program in any programming languageHow to write a program in any programming language
How to write a program in any programming language
Rakesh Kumar R
 
All you need to know about Spring Boot and GraalVM
All you need to know about Spring Boot and GraalVMAll you need to know about Spring Boot and GraalVM
All you need to know about Spring Boot and GraalVM
Alina Yurenko
 
Oracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptxOracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptx
Remote DBA Services
 
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
mz5nrf0n
 
Malibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed RoundMalibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed Round
sjcobrien
 
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
Łukasz Chruściel
 
GreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-JurisicGreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-Jurisic
Green Software Development
 

Recently uploaded (20)

Top 9 Trends in Cybersecurity for 2024.pptx
Top 9 Trends in Cybersecurity for 2024.pptxTop 9 Trends in Cybersecurity for 2024.pptx
Top 9 Trends in Cybersecurity for 2024.pptx
 
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdfTop Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
 
zOS Mainframe JES2-JES3 JCL-JECL Differences
zOS Mainframe JES2-JES3 JCL-JECL DifferenceszOS Mainframe JES2-JES3 JCL-JECL Differences
zOS Mainframe JES2-JES3 JCL-JECL Differences
 
Fundamentals of Programming and Language Processors
Fundamentals of Programming and Language ProcessorsFundamentals of Programming and Language Processors
Fundamentals of Programming and Language Processors
 
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
 
How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?
 
E-commerce Development Services- Hornet Dynamics
E-commerce Development Services- Hornet DynamicsE-commerce Development Services- Hornet Dynamics
E-commerce Development Services- Hornet Dynamics
 
Measures in SQL (SIGMOD 2024, Santiago, Chile)
Measures in SQL (SIGMOD 2024, Santiago, Chile)Measures in SQL (SIGMOD 2024, Santiago, Chile)
Measures in SQL (SIGMOD 2024, Santiago, Chile)
 
Artificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension FunctionsArtificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension Functions
 
Microservice Teams - How the cloud changes the way we work
Microservice Teams - How the cloud changes the way we workMicroservice Teams - How the cloud changes the way we work
Microservice Teams - How the cloud changes the way we work
 
Everything You Need to Know About X-Sign: The eSign Functionality of XfilesPr...
Everything You Need to Know About X-Sign: The eSign Functionality of XfilesPr...Everything You Need to Know About X-Sign: The eSign Functionality of XfilesPr...
Everything You Need to Know About X-Sign: The eSign Functionality of XfilesPr...
 
Energy consumption of Database Management - Florina Jonuzi
Energy consumption of Database Management - Florina JonuziEnergy consumption of Database Management - Florina Jonuzi
Energy consumption of Database Management - Florina Jonuzi
 
Oracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptxOracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptx
 
How to write a program in any programming language
How to write a program in any programming languageHow to write a program in any programming language
How to write a program in any programming language
 
All you need to know about Spring Boot and GraalVM
All you need to know about Spring Boot and GraalVMAll you need to know about Spring Boot and GraalVM
All you need to know about Spring Boot and GraalVM
 
Oracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptxOracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptx
 
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
 
Malibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed RoundMalibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed Round
 
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
 
GreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-JurisicGreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-Jurisic
 

SOLID principles in practice: the Clean Architecture - Devfest Emila Romagna

  • 1. SOLID principles in practice: the clean architecture Fabio Collini
  • 4.
  • 5.
  • 6. class WeatherUseCase( private val locationManager: LocationManager, private val repository: TemperatureRepository) { suspend fun getCityData(): String = coroutineScope { try { val location = locationManager.getLastLocation() val cities = async { locationManager.getCities(location) } val temperature = repository.getTemperature( location.lat, location.lon) val city = cities.await().getOrElse(0) { "No city found" } "$city n $temperature" } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3
  • 7. Coroutines class WeatherUseCase( private val locationManager: LocationManager, private val repository: TemperatureRepository) { suspend fun getCityData(): String = coroutineScope { try { val location = locationManager.getLastLocation() val cities = async { locationManager.getCities(location) } val temperature = repository.getTemperature( location.lat, location.lon) val city = cities.await().getOrElse(0) { "No city found" } "$city n $temperature" } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3
  • 8. Dependency Injection class WeatherUseCase( private val locationManager: LocationManager, private val repository: TemperatureRepository) { suspend fun getCityData(): String = coroutineScope { try { val location = locationManager.getLastLocation() val cities = async { locationManager.getCities(location) } val temperature = repository.getTemperature( location.lat, location.lon) val city = cities.await().getOrElse(0) { "No city found" } "$city n $temperature" } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3
  • 9. class WeatherUseCaseTest { val locationManager: LocationManager = mockk() val repository: TemperatureRepository = mockk() val useCase = WeatherUseCase(locationManager, repository) @Test fun retrieveCityData() { coEvery { locationManager.getLastLocation() } returns LOCATION coEvery { locationManager.getCities(LOCATION) } returns listOf(City("Firenze", "IT")) coEvery { repository.getTemperature(LAT, LON) } returns Temperature(10, 8, 20) val cityData = runBlocking { useCase.getCityData() } assert(cityData).isEqualTo("Firenze (IT) n 10º min 8º max 20º") }1 }2
  • 10. interface LocationManager { suspend fun getLastLocation(): Location suspend fun getCities(location: Location): List<City> }6 class WeatherUseCase( private val locationManager: LocationManager, private val repository: TemperatureRepository) { suspend fun getCityData(): String = coroutineScope { try { val location = locationManager.getLastLocation() val cities = async { locationManager.getCities(location) } val temperature = repository.getTemperature( location.lat, location.lon) val city = cities.await().getOrElse(0) { "No city found" } "$city n $temperature" } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3 interface TemperatureRepository { suspend fun getTemperature(lat: Double, lon: Double): Temperature }7
  • 11. interface LocationManager { suspend fun getLastLocation(): Location suspend fun getCities(location: Location): List<City> }6 class AndroidLocationManager(context: Context) : LocationManager { private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) private val geocoder = Geocoder(context, Locale.getDefault()) override suspend fun getLastLocation(): Location = suspendCoroutine { //... }E override suspend fun getCities(location: Location): List<City> = suspendCoroutine { //... }E }E interface TemperatureRepository { suspend fun getTemperature(lat: Double, lon: Double): Temperature }7 class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(lat: Double, lon: Double): Temperature { val forecastDeferred = api.forecast(lat, lon) val weather = api.currentWeather(lat, lon).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F
  • 12. class AndroidLocationManager(context: Context) : LocationManager { private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) private val geocoder = Geocoder(context, Locale.getDefault()) override suspend fun getLastLocation(): Location = suspendCoroutine { //... }E override suspend fun getCities(location: Location): List<City> = suspendCoroutine { //... }E }E
  • 13. class AndroidLocationManager(context: Context) : LocationManager { private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) private val geocoder = Geocoder(context, Locale.getDefault()) @SuppressLint("MissingPermission") override suspend fun getLastLocation(): Location = suspendCoroutine { continuation -> fusedLocationClient.lastLocation .addOnSuccessListener { location -> if (location == null) continuation.resumeWithException(Exception("Location not available")) else continuation.resume(Location(location.latitude, location.longitude)) } .addOnFailureListener { continuation.resumeWithException(it) } }E override suspend fun getCities(location: Location): List<City> = suspendCoroutine { continuation -> val addresses = geocoder.getFromLocation(location.lat, location.lon, 10) continuation.resume(addresses .filter { it.locality != null } .map { City(it.locality, it.countryCode) } ) }E }E
  • 14. interface LocationManager { suspend fun getLastLocation(): Location suspend fun getCities(location: Location): List<City> }6 class AndroidLocationManager(context: Context) : LocationManager { private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) private val geocoder = Geocoder(context, Locale.getDefault()) override suspend fun getLastLocation(): Location = suspendCoroutine { //... }E override suspend fun getCities(location: Location): List<City> = suspendCoroutine { //... }E }E interface TemperatureRepository { suspend fun getTemperature(lat: Double, lon: Double): Temperature }7 class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(lat: Double, lon: Double): Temperature { val forecastDeferred = api.forecast(lat, lon) val weather = api.currentWeather(lat, lon).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F
  • 15. class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(lat: Double, lon: Double): Temperature { val forecastDeferred = api.forecast(lat, lon) val weather = api.currentWeather(lat, lon).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F
  • 16. class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(lat: Double, lon: Double): Temperature { val forecastDeferred = api.forecast(lat, lon) val weather = api.currentWeather(lat, lon).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F
  • 17. interface LocationManager { suspend fun getLastLocation(): Location suspend fun getCities(location: Location): List<City> }6 class AndroidLocationManager(context: Context) : LocationManager { private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) private val geocoder = Geocoder(context, Locale.getDefault()) override suspend fun getLastLocation(): Location = suspendCoroutine { //... }E override suspend fun getCities(location: Location): List<City> = suspendCoroutine { //... }E }E interface TemperatureRepository { suspend fun getTemperature(lat: Double, lon: Double): Temperature }7 class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(lat: Double, lon: Double): Temperature { val forecastDeferred = api.forecast(lat, lon) val weather = api.currentWeather(lat, lon).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F
  • 18. interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<Forecast> }Z interface LocationManager { suspend fun getLastLocation(): Location suspend fun getCities(location: Location): List<City> }6 class AndroidLocationManager(context: Context) : LocationManager { private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) private val geocoder = Geocoder(context, Locale.getDefault()) override suspend fun getLastLocation(): Location = suspendCoroutine { //... }E override suspend fun getCities(location: Location): List<City> = suspendCoroutine { //... }E }E interface TemperatureRepository { suspend fun getTemperature(lat: Double, lon: Double): Temperature }7 class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(lat: Double, lon: Double): Temperature { val forecastDeferred = api.forecast(lat, lon) val weather = api.currentWeather(lat, lon).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F
  • 19. interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<Forecast> }Z
  • 20. interface TemperatureRepository { suspend fun getTemperature(lat: Double, lon: Double): Temperature }7 interface LocationManager { suspend fun getLastLocation(): Location suspend fun getCities(location: Location): List<City> }6 class AndroidLocationManager(context: Context) : LocationManager { private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) private val geocoder = Geocoder(context, Locale.getDefault()) override suspend fun getLastLocation(): Location = suspendCoroutine { //... }E override suspend fun getCities(location: Location): List<City> = suspendCoroutine { //... }E }E class WeatherUseCase( private val locationManager: LocationManager, private val repository: TemperatureRepository) { suspend fun getCityData(): String = coroutineScope { try { val location = locationManager.getLastLocation() val cities = async { locationManager.getCities(location) } val temperature = repository.getTemperature( location.lat, location.lon) val city = cities.await().getOrElse(0) { "No city found" } "$city n $temperature" } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3 interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<Forecast> }Z class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(lat: Double, lon: Double): Temperature { val forecastDeferred = api.forecast(lat, lon) val weather = api.currentWeather(lat, lon).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F
  • 21. class WeatherUseCase( private val locationManager: LocationManager, private val repository: TemperatureRepository) { suspend fun getCityData(): String = coroutineScope { try { val location = locationManager.getLastLocation() val cities = async { locationManager.getCities(location) } val temperature = repository.getTemperature( location.lat, location.lon) val city = cities.await().getOrElse(0) { "No city found" } "$city n $temperature" } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3 class WeatherViewModel(app: Application) : AndroidViewModel(app) { private val api = RetrofitFactory.createService<WeatherApi>() private val weatherRepository = OpenWeatherTemperatureRepository(api) private val positionManager = AndroidLocationManager(app) private val useCase = WeatherUseCase(positionManager, weatherRepository) val state = MutableLiveData<String>() fun load() { viewModelScope.launch { val result = useCase.getCityData() state.value = result }O }O }O class MainActivity : AppCompatActivity() { //... } interface LocationManager { suspend fun getLastLocation(): Location suspend fun getCities(location: Location): List<City> }6 class AndroidLocationManager(context: Context) : LocationManager { private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) private val geocoder = Geocoder(context, Locale.getDefault()) override suspend fun getLastLocation(): Location = suspendCoroutine { //... }E override suspend fun getCities(location: Location): List<City> = suspendCoroutine { //... }E }E interface TemperatureRepository { suspend fun getTemperature(lat: Double, lon: Double): Temperature }7 interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<Forecast> }Z class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(lat: Double, lon: Double): Temperature { val forecastDeferred = api.forecast(lat, lon) val weather = api.currentWeather(lat, lon).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F
  • 22. class WeatherViewModel(app: Application) : AndroidViewModel(app) { private val api = RetrofitFactory.createService<WeatherApi>() private val weatherRepository = OpenWeatherTemperatureRepository(api) private val positionManager = AndroidLocationManager(app) private val useCase = WeatherUseCase(positionManager, weatherRepository) val state = MutableLiveData<String>() fun load() { viewModelScope.launch { val result = useCase.getCityData() state.value = result }O }O }O
  • 23. app api domain weather location interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<Forecast> }Z class WeatherUseCase( private val locationManager: LocationManager, private val repository: TemperatureRepository) { suspend fun getCityData(): String = coroutineScope { try { val location = locationManager.getLastLocation() val cities = async { locationManager.getCities(location) } val temperature = repository.getTemperature( location.lat, location.lon) val city = cities.await().getOrElse(0) { "No city found" } "$city n $temperature" } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3 class MainActivity : AppCompatActivity() { //... } interface LocationManager { suspend fun getLastLocation(): Location suspend fun getCities(location: Location): List<City> }6 class AndroidLocationManager(context: Context) : LocationManager { private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) private val geocoder = Geocoder(context, Locale.getDefault()) override suspend fun getLastLocation(): Location = suspendCoroutine { //... }E override suspend fun getCities(location: Location): List<City> = suspendCoroutine { //... }E }E interface TemperatureRepository { suspend fun getTemperature(lat: Double, lon: Double): Temperature }7 class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(lat: Double, lon: Double): Temperature { val forecastDeferred = api.forecast(lat, lon) val weather = api.currentWeather(lat, lon).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F class WeatherViewModel(app: Application) : AndroidViewModel(app) { private val api = RetrofitFactory.createService<WeatherApi>() private val weatherRepository = OpenWeatherTemperatureRepository(api) private val positionManager = AndroidLocationManager(app) private val useCase = WeatherUseCase(positionManager, weatherRepository) val state = MutableLiveData<String>() fun load() { viewModelScope.launch { val result = useCase.getCityData() state.value = result }O }O }O
  • 24. Java/Kotlin modules are more… Testable Reusable
  • 25. Robert C. Martin Copyright (c) 2000 by Robert C. Martin. All Rights Reserved. www.objectmentor.com 1 Design Principles and Design Patterns Robert C. Martin www.objectmentor.com What is software architecture? The answer is multitiered. At the highest level, there are the architecture patterns that define the overall shape and structure of software applications1 . Down a level is the architecture that is specifically related to the pur- pose of the software application. Yet another level down resides the architecture of the modules and their interconnections. This is the domain of design patterns2 , pack- akges, components, and classes. It is this level that we will concern ourselves with in this chapter. Our scope in this chapter is quite limitted. There is much more to be said about the principles and patterns that are exposed here. Interested readers are referred to [Martin99]. Architecture and Dependencies What goes wrong with software? The design of many software applications begins as a vital image in the minds of its designers. At this stage it is clean, elegant, and com- pelling. It has a simple beauty that makes the designers and implementers itch to see it working. Some of these applications manage to maintain this purity of design through the initial development and into the first release. But then something begins to happen. The software starts to rot. At first it isn’t so bad. An ugly wart here, a clumsy hack there, but the beauty of the design still shows through. Yet, over time as the rotting continues, the ugly festering sores and boils accumulate until they dominate the design of the application. The program becomes a festering mass of code that the developers find increasingly hard to maintain. Eventu- 1. [Shaw96] 2. [GOF96]
  • 26. Copyright (c) 2000 by Robert C. Martin. All Rights Reserved.
  • 28. Single responsibility “A class should have one, and only one, reason to change”
  • 29. app domain api weather interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<Forecast> }Z interface TemperatureRepository { suspend fun getTemperature(lat: Double, lon: Double): Temperature }7 class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(lat: Double, lon: Double): Temperature { val forecastDeferred = api.forecast(lat, lon) val weather = api.currentWeather(lat, lon).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F class WeatherUseCase( private val locationManager: LocationManager, private val repository: TemperatureRepository) { suspend fun getCityData(): String = coroutineScope { try { val location = locationManager.getLastLocation() val cities = async { locationManager.getCities(location) } val temperature = repository.getTemperature( location.lat, location.lon) val city = cities.await().getOrElse(0) { "No city found" } "$city n $temperature" } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3 class WeatherViewModel(app: Application) : AndroidViewModel(app) { private val api = RetrofitFactory.createService<WeatherApi>() private val weatherRepository = OpenWeatherTemperatureRepository(api) private val positionManager = AndroidLocationManager(app) private val useCase = WeatherUseCase(positionManager, weatherRepository) val state = MutableLiveData<String>() fun load() { viewModelScope.launch { val result = useCase.getCityData() state.value = result }O }O }O class MainActivity : AppCompatActivity() { //... } location interface LocationManager { suspend fun getLastLocation(): Location suspend fun getCities(location: Location): List<City> }6 class AndroidLocationManager(context: Context) : LocationManager { private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) private val geocoder = Geocoder(context, Locale.getDefault()) override suspend fun getLastLocation(): Location = suspendCoroutine { //... }E override suspend fun getCities(location: Location): List<City> = suspendCoroutine { //... }E }E
  • 31. Open closed “You should be able to extend a classes behavior, without modifying it”
  • 35. Liskov substitution “Derived classes must be substitutable for their base classes”
  • 36. open class Rectangle( open var width: Int, open var height: Int ) { fun area() = width * height } class Square(size: Int) : Rectangle(size, size) { override var width: Int = size set(value) { field = value if (height != value) height = value } override var height: Int = size set(value) { field = value if (width != value) width = value } }
  • 37. var rectangle = Rectangle(2, 3) var initialArea = rectangle.area() rectangle.width *= 2 println(initialArea * 2 == rectangle.area()) rectangle = Square(2) initialArea = rectangle.area() rectangle.width *= 2 println(initialArea * 2 == rectangle.area())
  • 39. Interface segregation “Make fine grained interfaces that are client specific”
  • 43. Dependency inversion “Depend on abstractions, not on concretions”
  • 44. app domain api weather interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<Forecast> }Z interface TemperatureRepository { suspend fun getTemperature(lat: Double, lon: Double): Temperature }7 class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(lat: Double, lon: Double): Temperature { val forecastDeferred = api.forecast(lat, lon) val weather = api.currentWeather(lat, lon).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F class WeatherUseCase( private val locationManager: LocationManager, private val repository: TemperatureRepository) { suspend fun getCityData(): String = coroutineScope { try { val location = locationManager.getLastLocation() val cities = async { locationManager.getCities(location) } val temperature = repository.getTemperature( location.lat, location.lon) val city = cities.await().getOrElse(0) { "No city found" } "$city n $temperature" } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3 class WeatherViewModel(app: Application) : AndroidViewModel(app) { private val api = RetrofitFactory.createService<WeatherApi>() private val weatherRepository = OpenWeatherTemperatureRepository(api) private val positionManager = AndroidLocationManager(app) private val useCase = WeatherUseCase(positionManager, weatherRepository) val state = MutableLiveData<String>() fun load() { viewModelScope.launch { val result = useCase.getCityData() state.value = result }O }O }O class MainActivity : AppCompatActivity() { //... } location interface LocationManager { suspend fun getLastLocation(): Location suspend fun getCities(location: Location): List<City> }6 class AndroidLocationManager(context: Context) : LocationManager { private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) private val geocoder = Geocoder(context, Locale.getDefault()) override suspend fun getLastLocation(): Location = suspendCoroutine { //... }E override suspend fun getCities(location: Location): List<City> = suspendCoroutine { //... }E }E
  • 45. Dependency inversion “Depend on abstractions, not on concretions”
  • 46. Dependency inversion “High level modules should not depend upon low level modules. Both should depend upon abstractions”
  • 47. app domain api weather interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<Forecast> }Z interface TemperatureRepository { suspend fun getTemperature(lat: Double, lon: Double): Temperature }7 class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(lat: Double, lon: Double): Temperature { val forecastDeferred = api.forecast(lat, lon) val weather = api.currentWeather(lat, lon).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F class WeatherUseCase( private val locationManager: LocationManager, private val repository: TemperatureRepository) { suspend fun getCityData(): String = coroutineScope { try { val location = locationManager.getLastLocation() val cities = async { locationManager.getCities(location) } val temperature = repository.getTemperature( location.lat, location.lon) val city = cities.await().getOrElse(0) { "No city found" } "$city n $temperature" } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3 class WeatherViewModel(app: Application) : AndroidViewModel(app) { private val api = RetrofitFactory.createService<WeatherApi>() private val weatherRepository = OpenWeatherTemperatureRepository(api) private val positionManager = AndroidLocationManager(app) private val useCase = WeatherUseCase(positionManager, weatherRepository) val state = MutableLiveData<String>() fun load() { viewModelScope.launch { val result = useCase.getCityData() state.value = result }O }O }O class MainActivity : AppCompatActivity() { //... } location interface LocationManager { suspend fun getLastLocation(): Location suspend fun getCities(location: Location): List<City> }6 class AndroidLocationManager(context: Context) : LocationManager { private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) private val geocoder = Geocoder(context, Locale.getDefault()) override suspend fun getLastLocation(): Location = suspendCoroutine { //... }E override suspend fun getCities(location: Location): List<City> = suspendCoroutine { //... }E }E
  • 48. domain location interface LocationManager { suspend fun getLastLocation(): Location suspend fun getCities(location: Location): List<City> }6 class AndroidLocationManager(context: Context) : LocationManager { private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) private val geocoder = Geocoder(context, Locale.getDefault()) override suspend fun getLastLocation(): Location = suspendCoroutine { //... }E override suspend fun getCities(location: Location): List<City> = suspendCoroutine { //... }E }E class WeatherUseCase( private val locationManager: LocationManager, private val repository: TemperatureRepository) { suspend fun getCityData(): String = coroutineScope { try { val location = locationManager.getLastLocation() val cities = async { locationManager.getCities(location) } val temperature = repository.getTemperature( location.lat, location.lon) val city = cities.await().getOrElse(0) { "No city found" } "$city n $temperature" } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3
  • 49. domain location class WeatherUseCase( private val locationManager: LocationManager, private val repository: TemperatureRepository) { suspend fun getCityData(): String = coroutineScope { try { val location = locationManager.getLastLocation() val cities = async { locationManager.getCities(location) } val temperature = repository.getTemperature( location.lat, location.lon) val city = cities.await().getOrElse(0) { "No city found" } "$city n $temperature" } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3 interface LocationManager { suspend fun getLastLocation(): Location suspend fun getCities(location: Location): List<City> }6 class AndroidLocationManager(context: Context) : LocationManager { private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) private val geocoder = Geocoder(context, Locale.getDefault()) override suspend fun getLastLocation(): Location = suspendCoroutine { //... }E override suspend fun getCities(location: Location): List<City> = suspendCoroutine { //... }E }E
  • 50. app domain api weather interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("lat") lat: Double, @Query("lon") lon: Double): Deferred<Forecast> }Z interface TemperatureRepository { suspend fun getTemperature(lat: Double, lon: Double): Temperature }7 class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(lat: Double, lon: Double): Temperature { val forecastDeferred = api.forecast(lat, lon) val weather = api.currentWeather(lat, lon).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F location class WeatherUseCase( private val locationManager: LocationManager, private val repository: TemperatureRepository) { suspend fun getCityData(): String = coroutineScope { try { val location = locationManager.getLastLocation() val cities = async { locationManager.getCities(location) } val temperature = repository.getTemperature( location.lat, location.lon) val city = cities.await().getOrElse(0) { "No city found" } "$city n $temperature" } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3 interface LocationManager { suspend fun getLastLocation(): Location suspend fun getCities(location: Location): List<City> }6 class AndroidLocationManager(context: Context) : LocationManager { private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) private val geocoder = Geocoder(context, Locale.getDefault()) override suspend fun getLastLocation(): Location = suspendCoroutine { //... }E override suspend fun getCities(location: Location): List<City> = suspendCoroutine { //... }E }E class WeatherViewModel(app: Application) : AndroidViewModel(app) { private val api = RetrofitFactory.createService<WeatherApi>() private val weatherRepository = OpenWeatherTemperatureRepository(api) private val positionManager = AndroidLocationManager(app) private val useCase = WeatherUseCase(positionManager, weatherRepository) val state = MutableLiveData<String>() fun load() { viewModelScope.launch { val result = useCase.getCityData() state.value = result }O }O }O class MainActivity : AppCompatActivity() { //... }
  • 61.
  • 65. Architecture Vs code conventions TDD Pros Cons More code Framework independence entitiesentitiesentitiesentities domain repository UI data source presenter
  • 66. Links Demo Project github.com/fabioCollini/CleanWeather 2000 - Robert C. Martin - Design Principles and Design Patterns www.cvc.uab.es/shared/teach/a21291/temes/object_oriented_design/ materials_adicionals/principles_and_patterns.pdf 2005 - Robert C. Martin - The Principles of OOD butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod 2012 - Robert C. Martin - The Clean Architecture 8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html