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

Android Clean Architecture

Android Clean Architecture

Handling lifecycle events, maintaining view state, and persisting data are all common challenges on Android that have contributed to the widespread adoption of clean architecture patterns like Model-View-Presenter (MVP) and Model-View-ViewModel (MVVM).

Android Architecture Components is a new collection of libraries announced at Google I/O to help developers manage these same nagging issues. This talk explores how Architecture Components can be leveraged in an app already using clean architecture principles to help make your code even more flexible, maintainable, and testable.

This talk was presented at GDG coast Lebanon event.
https://www.meetup.com/GDG-Coast-Lebanon/events/250065254/

Mohammed Touban

April 27, 2018
Tweet

More Decks by Mohammed Touban

Other Decks in Technology

Transcript

  1. View Activity Web Service Activity Lifecycle System Services User Input

    Dependency Injection Repository Storage Presenter View EditText EditText Button
  2. Clean Architecture Separation of Concerns Build layered applications with clear

    separation of responsibilities including view, presentation, and domain logic. State Synchronization Synchronize data across screen state, session state, and server state. Testability Enables pure unit tests to test presentation logic and domain models in isolation. Integration and UI tests for components that interact with the framework and/or display.
  3. “Those who do not remember the past are condemned to

    repeat it.” — George Santayana
  4. Clean Architecture Separation of Concerns Build layered applications with clear

    separation of responsibilities including view, presentation, and domain logic. State Synchronization Synchronize data across screen state, session state, and server state. Testability Enables pure unit tests to test presentation logic and domain models in isolation. Integration and UI tests for components that interact with the framework and/or display.
  5. Clean Architecture Separation of Concerns Build layered applications with clear

    separation of responsibilities including view, presentation, and domain logic. State Synchronization Synchronize data across screen state, session state, and server state. Testability Enables pure unit tests to test presentation logic and domain models in isolation. Integration and UI tests for components that interact with the framework and/or display.
  6. Clean Architecture Separation of Concerns Build layered applications with clear

    separation of responsibilities including view, presentation, and domain logic. State Synchronization Synchronize data across screen state, session state, and server state. Testability Enables pure unit tests to test presentation logic and domain models in isolation. Integration and UI tests for components that interact with the framework and/or display.
  7. T estability • • • Android framework dependencies encapsulated in

    view layer Pure JVM unit tests for presenters and domain models Espresso, Robolectric, or UI Automator tests for view layer
  8. Clean Architecture Separation of Concerns Build layered applications with clear

    separation of responsibilities including view, presentation, and domain logic. State Synchronization Synchronize data across screen state, session state, and server state. Testability Enables pure unit tests to test presentation logic and domain models in isolation. Integration and UI tests for components that interact with the framework and/or display.
  9. Supervising Controller (MVC) • • • • Smalltalk-80 One way

    flow of information Controller handles user input and UI events View observes changes in domain model State Sync: Data Binding Controller Model onLoginButtonClick() setUser() View View.onClick()
  10. Passive View (MVP) • • • • Also called Humble

    View No dependency between view and domain model Bi-directional flow of information View replaced by fake in unit tests State Sync: Flow Synchronization View View.onClick() showUser() Presenter onLoginButtonClick() Model getUser() View showUser()
  11. class LoginPresenter(private val view: LoginView) { fun onLoginButtonClick(email: String?, password:

    String?) { if (email != null && password != null) { val user = Model.getUser(email, password) if (user != null) { view.showUser(user) } else { view.showError(“Invalid credentials") } } } } LoginPresenter.kt
  12. Presentation Model (MVVM) • • • View forwards user input

    to presentation model Presentation model mutates state using domain model View observes changes in the presentation model State Sync: Observer Pattern ViewModel Model onLoginButtonClick() getUser() View View.onClick() onUpdateUser( ) updateUser()
  13. Supervising Controller (MVC) Passive View (MVP ) Presentation Model (MVVM

    ) Flow of Information One way Two way Two way Model <> View Dependency Yes No No Sync Logic View Presenter View State Synchronization Data binding Flow synchronization Observer pattern GUI Architectures
  14. • • • • Passive view migrates all sync logic

    into presentation layer MVC and MVVM require sync logic in the view layer For simpler apps, MVC or MVVM may be sufficient MVP tests are more verbose and require a fake view Humble View is the real MVP
  15. Android Passive View Presenter Activity Presentation Logic Model Domain Logic

    User Interface User Input Lifecycle Events System Services View
  16. Android Passive View Presenter Activity Presentation Logic Model Domain Logic

    View User Interface User Input Lifecycle Events System Services View
  17. Android Passive View Presenter Activity Presentation Logic Controller Model Domain

    Logic View User Interface User Input Lifecycle Events System Services
  18. Android Passive View Presenter Activity Presentation Logic Controller Model Domain

    Logic View User Interface User Input Lifecycle Events System Services
  19. Presenter Fake Controller Presentation Logic Controller Model Domain Logic Stub

    Implementation Passive View: Unit Tests Presenter Test Test Cases Model Test Test Cases
  20. Presenter Activity Presentation Logic Controller Model Domain Logic View User

    Interface User Input Lifecycle Events System Services Passive View: UI Tests Activity Test Test Cases
  21. class LoginActivity : AppCompatActivity(), LoginView { lateinit var presenter: LoginPresenter

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) presenter = LoginPresenter(this) login_button.setOnClickListener({ presenter.onLoginButtonClick(login_view.email, login_view.password) }) } // ... } LoginActivity.kt
  22. class LoginView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr:

    Int = 0) : LinearLayout(context, attrs, defStyleAttr) { var email: String? = null get() = login_email.text.toString() var password: String? = null get() = login_password.text.toString() var error: String? = null set(value) { login_error.text = value } init { (getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater).inflate(R.layout.view_login, this, true) } } LoginView.kt
  23. <EditText android:id="@+id/login_email" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <EditText android:id="@+id/login_password" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <Button android:id="@+id/login_button"

    android:layout_width="wrap_content" android:layout_height=“wrap_content"/> <TextView android:id=“@+id/login_error" android:layout_width="wrap_content" android:layout_height="wrap_content"/> view_login.xml
  24. class LoginPresenter(private val view: LoginView) { fun onLoginButtonClick(email: String?, password:

    String?) { if (email != null && password != null) { // … get user from REST API call if (user != null) { view.showUser(user) } else { view.showError(“Invalid credentials") } } } } LoginPresenter.kt
  25. class LoginActivity : AppCompatActivity(), LoginView{ // ... override fun showUser(user:

    User) { login_view.visibility = View.GONE user_view.visibility = View.VISIBLE user_view.user = user } override fun showError(error: String) { login_view.error = error } } LoginActivity.kt
  26. class UserView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr:

    Int = 0) : LinearLayout(context, attrs, defStyleAttr) { var user: User? = null set(value) { user_name.text = value?.name user_email.text = value?.email } init { (getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater).inflate(R.layout.view_user, this, true); } } UserView.kt
  27. Android-y challenges • • • • • Foreground/background events Configuration

    change Presenter scope Long-running operations Data persistence
  28. Controller LifecycleActivity Web Service Activity Lifecycle System Services User Input

    Dependency Injection Repository Model Model Storage Presenter View EditText EditText Button
  29. Controller LifecycleActivity Web Service Activity Lifecycle System Services User Input

    Dependency Injection Repository Model LiveData Room Storage Presenter ViewModel View EditText EditText Button
  30. Some thoughts for the future… • • • • •

    Community-driven architecture Mix and match components Fragments? Lifecycle aware libraries We are all in this together