Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dependency Inversion from S to D (SOLID)

Dependency Inversion from S to D (SOLID)

Made together with this awesome developer, https://speakerdeck.com/cdmunoz.

Solid introduction talk to explain why abstractions, encapsulation and OO concepts works together to build better software.

https://www.youtube.com/watch?v=gy2i5GSxqXA

Daniel

May 12, 2021
Tweet

More Decks by Daniel

Other Decks in Technology

Transcript

  1. 11 data class User( val email: String, val name: String,

    val id: String ) interface LoginService { @POST("login") fun login(@Query("email") email: String?, @Query("password") password: String?) : Call<User>? } class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<Button>(R.id.login_button).setOnClickListener { val progressBar = findViewById<View>(R.id.login_progress).apply { visibility = View.VISIBLE } val email = findViewById<EditText>(R.id.login_email).text.toString() val password = findViewById<EditText>(R.id.login_password).text.toString() val retrofit = Retrofit.Builder() .client(OkHttpClient.Builder() .addInterceptor( HttpLoggingInterceptor().apply { level = (HttpLoggingInterceptor.Level.BODY) } ).build() ) .baseUrl("https://602db10196eaad00176dcb6d.mockapi.io") .addConverterFactory(MoshiConverterFactory.create()) .build() val service: LoginService = retrofit.create(LoginService::class.java) val call = service.login(email, password) call?.enqueue(object : Callback<User> { override fun onResponse(call: Call<User>, response: Response<User>) { progressBar.visibility = View.GONE if (response.isSuccessful) { val user = response.body() AlertDialog.Builder(this@MainActivity) .setTitle("Hola!") .setMessage("Bienvenido ${user?.name ?: ""}!") .setPositiveButton(android.R.string.yes, null) .setNegativeButton(android.R.string.no, null) .show() } else { https://github.com/ MedellinAndroid/ dependency_inversion_example
  2. LoginActivity login_activity.xml Android Lifecycle (onCreate, ...) Android View Elements (Button,

    TextField, etc...) HTTP Client Third Party API Service Response/Request serialization (JSON, XML, ...) HTTP Error handling HTTP Status Code UserModel Logica
  3. 1. Qué hace actualmente el MainActivity? - Cuál debería ser

    su responsabilidad? 2. Qué sucede si quiero hacer tests? 3. Qué sucede si quiero hacer login desde multiples vistas? 4. Si llega alguien nuevo al equipo (o se va...) 5. Cuál debería ser su responsabilidad? 6. Qué sucede si quiero agregar login social (google, fb, etc)? 7. Qué pasa si para desarrollar queremos tener logs para todos los requests? 8. Dónde vamos a guardar al usuario logeado? SharedPreferences? Room?
  4. 17 data class User( val email: String, val name: String,

    val id: String ) interface LoginService { @POST("login") fun login(@Query("email") email: String?, @Query("password") password: String?) : Call<User>? } class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<Button>(R.id.login_button).setOnClickListener { val progressBar = findViewById<View>(R.id.login_progress).apply { visibility = View.VISIBLE } val email = findViewById<EditText>(R.id.login_email).text.toString() val password = findViewById<EditText>(R.id.login_password).text.toString() val retrofit = Retrofit.Builder() .client(OkHttpClient.Builder() .addInterceptor( HttpLoggingInterceptor().apply { level = (HttpLoggingInterceptor.Level.BODY) } ).build() ) .baseUrl("https://602db10196eaad00176dcb6d.mockapi.io") .addConverterFactory(MoshiConverterFactory.create()) .build() val service: LoginService = retrofit.create(LoginService::class.java) val call = service.login(email, password) call?.enqueue(object : Callback<User> { override fun onResponse(call: Call<User>, response: Response<User>) { progressBar.visibility = View.GONE if (response.isSuccessful) { val user = response.body() AlertDialog.Builder(this@MainActivity) .setTitle("Hola!") .setMessage("Bienvenido ${user?.name ?: ""}!") .setPositiveButton(android.R.string.yes, null) .setNegativeButton(android.R.string.no, null) .show() } else {
  5. LoginActivity login_activity.xml Android Lifecycle (onCreate, ...) Android View Elements (Button,

    TextField, etc...) HTTP Client Third Party API Service Response/Request serialization (JSON, XML, ...) HTTP Error handling HTTP Status Code UserModel Logica
  6. The Magical Mystery Four: How is Working Memory Capacity Limited,

    and Why? https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2864034/ Working memory storage capacity is important because cognitive tasks can be completed only with su ff i cient ability to hold information as it is processed. The ability to repeat information depends on task demands but can be distinguished from a more constant, underlying mechanism: a central memory store limited to 3 to 5 meaningful items in young adults. [...] The Magical Mystery Four: How is Working Memory Capacity Limited, and Why?
  7. LoginActivity login_activity.xml Android Lifecycle (onCreate, ...) Android View Elements (Button,

    TextField, etc...) HTTP Client Third Party API Service Response/Request serialization (JSON, XML, ...) HTTP Error handling HTTP Status Code UserModel Logica
  8. UserRepository LoginActivity login_activity.xml Android Lifecycle (onCreate, ...) Android View Elements

    (Button, TextField, etc...) HTTP Client Third Party API Service Response/Request serialization (JSON, XML, ...) HTTP Error handling HTTP Status Code UserModel Logica
  9. class UserRepository { private val service: LoginService init { val

    retrofit = Retrofit.Builder() .client( OkHttpClient.Builder() .addInterceptor( HttpLoggingInterceptor().apply { level = (HttpLoggingInterceptor.Level.BODY) } ).build() ) .baseUrl("https://602db10196eaad00176dcb6d.mockapi.io") .addConverterFactory(MoshiConverterFactory.create()) .build() service = retrofit.create(LoginService::class.java) } fun login(email: String, password: String, responseCallback: (User?) -> Unit) { val call = service.login(email, password) call?.enqueue(object : Callback<User> { override fun onResponse(call: Call<User>, response: Response<User>) { if (response.isSuccessful) { val user = response.body() responseCallback(user) } else { responseCallback(null) } } override fun onFailure(call: Call<User>, t: Throwable) { responseCallback(null) } }) } }
  10. class UserRepository { private val service: LoginService init { val

    retrofit = Retrofit.Builder() .client( OkHttpClient.Builder() .addInterceptor( HttpLoggingInterceptor().apply { level = (HttpLoggingInterceptor.Level.BODY) } ).build() ) .baseUrl("https://602db10196eaad00176dcb6d.mockapi.io") .addConverterFactory(MoshiConverterFactory.create()) .build() service = retrofit.create(LoginService::class.java) } fun login(email: String, password: String, responseCallback: (User?) -> Unit) { val call = service.login(email, password) call?.enqueue(object : Callback<User> { override fun onResponse(call: Call<User>, response: Response<User>) { if (response.isSuccessful) { val user = response.body() responseCallback(user) } else { responseCallback(null) } } override fun onFailure(call: Call<User>, t: Throwable) { responseCallback(null) } }) } }
  11. class UserRepository(private val service: LoginService) { private val service: LoginService

    init { val retrofit = Retrofit.Builder() .client( OkHttpClient.Builder() .addInterceptor( HttpLoggingInterceptor().apply { level = (HttpLoggingInterceptor.Level.BODY) } ).build() ) .baseUrl("https://602db10196eaad00176dcb6d.mockapi.io") .addConverterFactory(MoshiConverterFactory.create()) .build() service = retrofit.create(LoginService::class.java) } fun login(email: String, password: String, responseCallback: (User?) -> Unit) { val call = service.login(email, password) call?.enqueue(object : Callback<User> { override fun onResponse(call: Call<User>, response: Response<User>) { if (response.isSuccessful) { val user = response.body() responseCallback(user) } else { responseCallback(null) } } override fun onFailure(call: Call<User>, t: Throwable) { responseCallback(null) } }) } }
  12. class UserRepository(private val service: LoginService) { fun login(email: String, password:

    String, responseCallback: (User?) -> Unit) { val call = service.login(email, password) call?.enqueue(object : Callback<User> { override fun onResponse(call: Call<User>, response: Response<User>) { if (response.isSuccessful) { val user = response.body() responseCallback(user) } else { responseCallback(null) } } override fun onFailure(call: Call<User>, t: Throwable) { responseCallback(null) } }) } }
  13. class Api { fun buildUserRepository(): UserRepository { val retrofit =

    Retrofit.Builder() .client( OkHttpClient.Builder() .addInterceptor( HttpLoggingInterceptor().apply { level = (HttpLoggingInterceptor.Level.BODY) } ).build() ) .baseUrl("https://602db10196eaad00176dcb6d.mockapi.io") .addConverterFactory(MoshiConverterFactory.create()) .build() val service: LoginService = retrofit.create(LoginService::class.java) return UserRepository(service) } }
  14. class Api { fun buildUserRepository(): UserRepository { val retrofit =

    Retrofit.Builder() .client( OkHttpClient.Builder() .addInterceptor( HttpLoggingInterceptor().apply { level = (HttpLoggingInterceptor.Level.BODY) } ).build() ) .baseUrl("https://602db10196eaad00176dcb6d.mockapi.io") .addConverterFactory(MoshiConverterFactory.create()) .build() val service: LoginService = retrofit.create(LoginService::class.java) return UserRepository(service) } }
  15. class Api { fun buildUserRepository(): UserRepository { val retrofit =

    Retrofit.Builder() .client( OkHttpClient.Builder() .addInterceptor( HttpLoggingInterceptor().apply { level = (HttpLoggingInterceptor.Level.BODY) } ).build() ) .baseUrl("https://602db10196eaad00176dcb6d.mockapi.io") .addConverterFactory(MoshiConverterFactory.create()) .build() val service: LoginService = retrofit.create(LoginService::class.java) return UserRepository(service) } } class UserRepository(private val service: LoginService) { fun login(email: String, password: String, responseCallback: (User?) -> Unit) { val call = service.login(email, password) call?.enqueue(object : Callback<User> { override fun onResponse(call: Call<User>, response: Response<User>) { if (response.isSuccessful) { val user = response.body() responseCallback(user) } else { responseCallback(null) } } override fun onFailure(call: Call<User>, t: Throwable) { responseCallback(null) } }) } }
  16. class Api { fun buildUserRepository(): UserRepository { val retrofit =

    Retrofit.Builder() .client( OkHttpClient.Builder() .addInterceptor( HttpLoggingInterceptor().apply { level = (HttpLoggingInterceptor.Level.BODY) } ).build() ) .baseUrl("https://602db10196eaad00176dcb6d.mockapi.io") .addConverterFactory(MoshiConverterFactory.create()) .build() val service: LoginService = retrofit.create(LoginService::class.java) return UserRepository(service) } } class UserRepository(private val service: LoginService) { fun login(email: String, password: String, responseCallback: (User?) -> Unit) { val call = service.login(email, password) call?.enqueue(object : Callback<User> { override fun onResponse(call: Call<User>, response: Response<User>) { if (response.isSuccessful) { val user = response.body() responseCallback(user) } else { responseCallback(null) } } override fun onFailure(call: Call<User>, t: Throwable) { responseCallback(null) } }) } }
  17. 33 data class User( val email: String, val name: String,

    val id: String ) interface LoginService { @POST("login") fun login(@Query("email") email: String?, @Query("password") password: String?) : Call<User>? } class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<Button>(R.id.login_button).setOnClickListener { val progressBar = findViewById<View>(R.id.login_progress).apply { visibility = View.VISIBLE } val email = findViewById<EditText>(R.id.login_email).text.toString() val password = findViewById<EditText>(R.id.login_password).text.toString() val retrofit = Retrofit.Builder() .client(OkHttpClient.Builder() .addInterceptor( HttpLoggingInterceptor().apply { level = (HttpLoggingInterceptor.Level.BODY) } ).build() ) .baseUrl("https://602db10196eaad00176dcb6d.mockapi.io") .addConverterFactory(MoshiConverterFactory.create()) .build() val service: LoginService = retrofit.create(LoginService::class.java) val call = service.login(email, password) call?.enqueue(object : Callback<User> { override fun onResponse(call: Call<User>, response: Response<User>) { progressBar.visibility = View.GONE if (response.isSuccessful) { val user = response.body() AlertDialog.Builder(this@MainActivity) .setTitle("Hola!") .setMessage("Bienvenido ${user?.name ?: ""}!") .setPositiveButton(android.R.string.yes, null) .setNegativeButton(android.R.string.no, null) .show() } else {
  18. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<Button>(R.id.login_button).setOnClickListener { val progressBar = findViewById<View>(R.id.login_progress).apply { visibility = View.VISIBLE } val email = findViewById<EditText>(R.id.login_email).text.toString() val password = findViewById<EditText>(R.id.login_password).text.toString() val userRepository = Api().buildUserRepository() userRepository.login(email, password) { progressBar.visibility = View.GONE if (it != null) { AlertDialog.Builder(this@MainActivity) .setTitle("Hola!") .setMessage("Bienvenido ${it.name}!") .setPositiveButton(android.R.string.yes, null) .setNegativeButton(android.R.string.no, null) .show() } else { AlertDialog.Builder(this@MainActivity) .setTitle("Error!") .setMessage("Algo paso 1!") .setPositiveButton(android.R.string.yes, null) .setNegativeButton(android.R.string.no, null) .show() } } } } }
  19. 35 data class User( val email: String, val name: String,

    val id: String ) interface LoginService { @POST("login") fun login(@Query("email") email: String?, @Query("password") password: String?) : Call<User>? } class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<Button>(R.id.login_button).setOnClickListener { val progressBar = findViewById<View>(R.id.login_progress).apply { visibility = View.VISIBLE } val email = findViewById<EditText>(R.id.login_email).text.toString() val password = findViewById<EditText>(R.id.login_password).text.toString() val retrofit = Retrofit.Builder() .client(OkHttpClient.Builder() .addInterceptor( HttpLoggingInterceptor().apply { level = (HttpLoggingInterceptor.Level.BODY) } ).build() ) .baseUrl("https://602db10196eaad00176dcb6d.mockapi.io") .addConverterFactory(MoshiConverterFactory.create()) .build() val service: LoginService = retrofit.create(LoginService::class.java) val call = service.login(email, password) call?.enqueue(object : Callback<User> { override fun onResponse(call: Call<User>, response: Response<User>) { progressBar.visibility = View.GONE if (response.isSuccessful) { val user = response.body() AlertDialog.Builder(this@MainActivity) .setTitle("Hola!") .setMessage("Bienvenido ${user?.name ?: ""}!") .setPositiveButton(android.R.string.yes, null) .setNegativeButton(android.R.string.no, null) .show() } else { AlertDialog.Builder(this@MainActivity) .setTitle("Error!") .setMessage("Algo paso 1!") .setPositiveButton(android.R.string.yes, null) .setNegativeButton(android.R.string.no, null) .show() } } override fun onFailure(call: Call<User>, t: Throwable) { progressBar.visibility = View.GONE Log.e("Login", "Error", t) AlertDialog.Builder(this@MainActivity) class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<Button>(R.id.login_button).setOnClickListener { val progressBar = findViewById<View>(R.id.login_progress).apply { visibility = View.VISIBLE } val email = findViewById<EditText>(R.id.login_email).text.toString() val password = findViewById<EditText>(R.id.login_password).text.toString() val userRepository = Api().buildUserRepository() userRepository.login(email, password) { progressBar.visibility = View.GONE if (it != null) { AlertDialog.Builder(this@MainActivity) .setTitle("Hola!") .setMessage("Bienvenido ${it.name}!") .setPositiveButton(android.R.string.yes, null) .setNegativeButton(android.R.string.no, null) .show() } else { AlertDialog.Builder(this@MainActivity) .setTitle("Error!") .setMessage("Algo paso 1!") .setPositiveButton(android.R.string.yes, null) .setNegativeButton(android.R.string.no, null) .show() } } } } }
  20. LoginActivity login_activity.xml Android Lifecycle (onCreate, ...) Android View Elements (Button,

    TextField, etc...) HTTP Client Third Party API Service Response/Request serialization (JSON, XML, ...) HTTP Error handling HTTP Status Code UserModel Navigation SharedPreferences Logica
  21. Service (ej retro fi t) UserRepository HTTP Client Third Party

    API Service Response/Request serialization (JSON, XML, ...) HTTP Error handling HTTP Status Code LoginActivity login_activity.xml Android Lifecycle (onCreate, ...) Android View Elements (Button, TextField, etc...) UserModel Logica
  22. Service (ej retro fi t) UserRepository HTTP Client Third Party

    API Service Response/Request serialization (JSON, XML, ...) HTTP Error handling HTTP Status Code Coordinator Navigation ViewModel/Presenter LoginActivity login_activity.xml Android Lifecycle (onCreate, ...) Android View Elements (Button, TextField, etc...) Logica UserModel
  23. LoginActivity login_activity.xml Android Lifecycle (onCreate, ...) Android View Elements (Button,

    TextField, etc...) HTTP Client Third Party API Service Response/Request serialization (JSON, XML, ...) HTTP Error handling HTTP Status Code UserModel Navigation SharedPreferences Logica
  24. Service (ej retro fi t) UserRepository HTTP Client Third Party

    API Service Response/Request serialization (JSON, XML, ...) HTTP Error handling HTTP Status Code Coordinator Navigation ViewModel/Presenter LoginActivity login_activity.xml Android Lifecycle (onCreate, ...) Android View Elements (Button, TextField, etc...) Logica UserModel
  25. Repository - Base: https://github.com/MedellinAndroid/dependency_inversion_example - With repositories: https://github.com/MedellinAndroid/ dependency_inversion_example/pull/1 -

    Software is details - Kevlin Henney: https://www.youtube.com/watch? v=kX0prJklhUE - Wikipedia: https://en.wikipedia.org/wiki/Dependency_inversion_principle - Imagenes: https://www.clarisoft.com/best-proven-method-scale-software-development- team/ https://developer.android.com/images/teach/hero.svg Referencias