Pro Yearly is on sale from $80 to $50! »

Architecture Components Distilled

Architecture Components Distilled

Presentation about Android Architecture Components, offering internal and practical considerations for Lifecycles, ViewModels and LiveDatas.

Presentation given at following events

- GDGSP Android Meetup #53
- GDG Devfest Paraná 2017

D4b7a3e2ed10f86e0b52498713ba2601?s=128

Ubiratan Soares

November 09, 2017
Tweet

Transcript

  1. ARCHITECTURE COMPONENTS DISTILLED Ubiratan Soares November / 2017

  2. None
  3. None
  4. None
  5. None
  6. None
  7. None
  8. SEVERAL YEARS LATER …

  9. None
  10. None
  11. AFTER SOME USAGE ...

  12. None
  13. LIFECYCLE ("the good")

  14. LIFECYCLE ROLE "A single abstraction on top of an Android

    component lifecycle (Activity, Fragment) that unifies both states and events transitions representations in a component-independent fashion"
  15. INITIALIZED CREATED STARTED RESUMED DESTROYED ON_CREATE ON_START ON_RESUME ON_DESTROY ON_PAUSE

    ON_STOP ENUMS
  16. LIFECYCLE OWNER FRAGMENT + getLifecycle( ) FRAGMENT ACTIVITY PROCESS LIFECYCLE

    + getCurrentState( ) + removeObserver( ) + addObserver( ) LIFECYCLE REGISTRY Dependency ๏ Colaborator Return Type ๏ Actual State ๏ Observers ๏ Parent States
  17. Favors composition over inheritance class YourLifecycleObserver : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_START)

    fun onStart() { // Some action at start } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onStop() { // Some action at stop } } // At your Activity / Fragment lifecycle.addObserver(YourLifecycleObserver())
  18. TYPICAL USAGES FOR OBSERVERS Auto-release for RxJava2 Disposables Auto bind/unbind

    presenters into/from their passive Views Auto register/unregister into/from some external framework, outside Activity/Fragment code Auto track some screen view at every screen resume ETC
  19. VIEWMODEL ("the ugly")

  20. VIEWMODEL ROLE "An abstraction that provides a brigde between an

    Android component (Activity, Fragment) and the "business logic" of the application, with a proper lifecycle scoped according the related component itself"
  21. class YourViewModel(val repository: SomeRepository) : ViewModel() { val items by

    lazy { repository.retrieveFromDatabase() } fun fetchData(): List<SomeData> { return items } }
  22. // Your Activity lateinit var viewModel : YourViewModel override fun

    onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_home) viewModel = ViewModelProviders.of(this) .get(YourViewModel::class.java) }
  23. // Your Activity lateinit var viewModel : YourViewModel override fun

    onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_home) viewModel = ViewModelProviders.of(this) .get(YourViewModel::class.java) }
  24. A CONSTRAINT If you want your ViewModel to have a

    non-default constructor, you must use a Factory to create it
  25. class YourViewModel(val repository: SomeRepository) : ViewModel() { fun fetchData(): List<SomeData>

    { return repository.retrieveFromNetwork() } } object YourViewModelFactory : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>?): T { val repository = // Retrieve your repository here return YourViewModel(repository) as T } }
  26. class YourViewModel(val repository: SomeRepository) : ViewModel() { fun fetchData(): List<SomeData>

    { return repository.retrieveFromNetwork() } } object YourViewModelFactory : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>?): T { val repository = // Retrieve your repository here return YourViewModel(repository) as T } }
  27. class YourViewModel(val repository: SomeRepository) : ViewModel() { fun fetchData(): List<SomeData>

    { return repository.retrieveFromNetwork() } } object YourViewModelFactory : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>?): T { val repository = // Retrieve your repository here return YourViewModel(repository) as T } }
  28. / Your Activity lateinit var viewModel : YourViewModel override fun

    onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_home) viewModel = ViewModelProviders.of(this, YourViewModelFactory) .get(YourViewModel::class.java) }
  29. finish( ) ViewModelProviders .of(this) .get(VMClass) onCleared( ) ✓ ON_CREATE ✓

    ON_START ✓ ON_RESUME ✓ ON_PAUSE ✓ ON_STOP ✓ ON_DESTROY ✓ ON_CREATE ✓ ON_START ✓ ON_RESUME ✓ ON_PAUSE ✓ ON_STOP ✓ ON_DESTROY ACTIVITY INSTANCE ACTIVITY INSTANCE INSTANCE LIFECYCLE Device Rotation ViewModelProviders .of(this) .get(VMClass) VIEWMODEL INSTANCE
  30. VIEW MODEL PROVIDERS ★ of(Activity) ★ of(Fragment) ★ of(Activity, Factory)

    ★ of(Fragment, Factory) VIEWMODEL PROVIDER + get(Class) VIEWMODEL STORE ๏ Map<String, ViewModel> VIEW MODEL STORES ★ holderFragmentFor(Activity) ★ holderFragmentFor(Fragment) ★ Static Factory Method + Public Instance Method ๏ Colaborator HOLDER FRAGMENT ๏ ViewModelStore + getViewModeStore( ) Return Type ๏ ViewModelProvider.Factory ๏ ViewModelStore
  31. VIEW MODEL PROVIDERS ★ of(Activity) ★ of(Fragment) ★ of(Activity, Factory)

    ★ of(Fragment, Factory) VIEWMODEL PROVIDER + get(Class) VIEWMODEL STORE ๏ Map<String, ViewModel> VIEW MODEL STORES ★ holderFragmentFor(Activity) ★ holderFragmentFor(Fragment) ★ Static Factory Method + Public Instance Method ๏ Colaborator HOLDER FRAGMENT ๏ ViewModelStore + getViewModeStore( ) Return Type ๏ ViewModelProvider.Factory ๏ ViewModelStore
  32. VIEWMODEL TL;DR A ViewModel has a dedicated lifecycle, which is

    provided according the scope of the related component A ViewModel scope will stand longer than the related Android component itself (if needed) : a ViewModel survives configuration changes, backed by a retained Fragment
  33. VIEWMODEL IN PRACTICE Should not store any reference for a

    passive view Should store and provide data related to user interface state Plays well with a single source of truth for data retrieving / processing (eg Repository Pattern) onCleared( ) can be used to release colaborators Should not have any Android framework related code
  34. // ViewModels.kt inline fun <reified VM : ViewModel> FragmentActivity.withFactory( crossinline

    factory: () -> ViewModelProvider.Factory) { ViewModelProviders.of(this, factory()).get(VM::class.java) }
  35. // ViewModels.kt inline fun <reified VM : ViewModel> FragmentActivity.withFactory( crossinline

    factory: () -> ViewModelProvider.Factory) { ViewModelProviders.of(this, factory()).get(VM::class.java) }
  36. // ViewModels.kt inline fun <reified VM : ViewModel> FragmentActivity.withFactory( crossinline

    factory: () -> ViewModelProvider.Factory) { ViewModelProviders.of(this, factory()).get(VM::class.java) }
  37. // SomeFactory.kt object SomeFactory { operator fun invoke(): ViewModelProvider.Factory {

    return object : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>?): T { val repository = // Retrieve your repository here! return YourViewModel(repository) as T } } } }
  38. // SomeFactory.kt object SomeFactory { operator fun invoke(): ViewModelProvider.Factory {

    return object : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>?): T { val repository = // Retrieve your repository here! return YourViewModel(repository) as T } } } }
  39. // SomeFactory.kt object SomeFactory { operator fun invoke(): ViewModelProvider.Factory {

    return object : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>?): T { val repository = // Retrieve your repository here! return YourViewModel(repository) as T } } } }
  40. KOTLIN AWESOMENESS // At your Activity val viewModel by withFactory<YourViewModel>

    { SomeFactory() }
  41. LIVEDATA ("the bad")

  42. LIVEDATA ROLE "A container that aims to provide the Observer

    Pattern for data, letting some observer be notified of data changes at proper lifecycle state (Started or Resumed)"
  43. interface SomeRepository { // Note : Room can return data

    as a LiveData !!! fun retrieveFromDatabase(): LiveData<List<SomeData>> }
  44. class YourViewModel( private val repository: SomeRepository) : ViewModel() { private

    val items : LiveData<List<SomeData>> by lazy { repository.retrieveFromDatabase() } fun fetchData(): LiveData<List<SomeData>> { return items } }
  45. class YourViewModel( private val repository: SomeRepository) : ViewModel() { private

    val items : LiveData<List<SomeData>> by lazy { repository.retrieveFromDatabase() } fun fetchData(): LiveData<List<SomeData>> { return items } }
  46. class YourViewModel( private val repository: SomeRepository) : ViewModel() { private

    val items : LiveData<List<SomeData>> by lazy { repository.retrieveFromDatabase() } fun fetchData(): LiveData<List<SomeData>> { return items } }
  47. // At your Activity / Fragment val viewModel by withFactory<YourViewModel>

    { SomeFactory() } viewModel .fetchData() .observe(this, Observer { // Update your UI here } )
  48. LIVEDATA TL;DR Publishes data updates to observers only at STARTED

    or RESUMED lifecycle states Ensures that a destroyed observer will not get any updates It not holds any data history, only the actual data state We can perform a few functional operations over LiveData via the Transformations API
  49. class YourViewModel( private val repository: SomeRepository) : ViewModel() { private

    val items: LiveData<List<SomeData>> by lazy { repository.retrieveFromDatabase() } fun fetchData(): LiveData<List<RowModel>> { return Transformations.map(items) { items -> toRowModels(items) } } } fun toRowModels(items: List<SomeData>): List<RowModel> { return items.map { it -> convertToRow(it) } }
  50. class YourViewModel( private val repository: SomeRepository) : ViewModel() { private

    val items: LiveData<List<SomeData>> by lazy { repository.retrieveFromDatabase() } fun fetchData(): LiveData<List<RowModel>> { return Transformations.map(items) { items -> toRowModels(items) } } } fun toRowModels(items: List<SomeData>): List<RowModel> { return items.map { it -> convertToRow(it) } }
  51. class YourViewModel( private val repository: SomeRepository) : ViewModel() { private

    val items: LiveData<List<SomeData>> by lazy { repository.retrieveFromDatabase() } fun fetchData(): LiveData<List<RowModel>> { return Transformations.map(items) { items -> toRowModels(items) } } } fun toRowModels(items: List<SomeData>): List<RowModel> { return items.map { it -> convertToRow(it) } }
  52. EXTENDING LIVEDATA "You should derive from LiveData only when you

    need to adapt some external, probably async data source at your repository implementation level"
  53. MutableLiveData LiveData MediatorLiveData "What is the difference all ??" vs

    vs
  54. IMPLEMENTATION INTENT LiveData<T> Stores some data in an immutable way

    MutableLiveData<T> Allows some client object to modify the contents of data - setValue( ) to update from the Android main thread - postValue( ) to update from another thread MediatorLiveData<T> An abstraction to combine two or more LiveDatas in a desired way
  55. LIVEDATA IN PRACTICE A RxJava for dummies the masses (???)

    Only publishes results on Android MainThread Offers a "bridge" to RxJava2 reactive types, but it should be avoided (personal opinion) Confusing roles for MutableLiveData and LiveData (personal opinion)
  56. HOW ABOUT STATE SAVING?

  57. WHEN AN ANDROID ACTIVITY DIES ???

  58. ACTIVITY DEATH REASON TRIGGER USER EXPECTATION Navigation, back or up

    USER App on a new state Swipe-off from recents USER App on a new state Explicit finish( ) USER (call to action) App on a new state Configuration changes (rotation, locale, etc) DEVICE / USER App on the same state as before Resources reclaim, application in background ANDROID SYSTEM App on the same state as before Process death in extreme conditions, app in background ANDROID SYSTEM App on the same state as before
  59. ViewModel Bundle onSaveInstanceState( ) Storage (DB, Files, Prefs) "Which approach

    should I use for effective screen state saving after all ???"
  60. MAYBE ALL OF THEM !!!

  61. "Bundle, ViewModel or Local storage may serve as holder for

    the actual screen state, but each one of them has pros and cons to be dealt with. A comprehensive strategy may use all three ways together : the answer will be given by the application context"
  62. STATE SAVING STRATEGY MAY HANDLE RESOURCES RECLAIMS ? HANDLE CONFIG

    CHANGES ? IS DATA READY FOR THE SCREEN ? AMOUNT OF DATA AVAILABLE RESISTS SYSTEM CRASHING / REBOOT DEMANDS IO ACCESS? Bundle YES YES PROBABLY YES SMALL (order of KB) NO NO ViewModel NO (Process Death) YES PROBABLY YES LARGE (order of MB) NO NO Local Storage YES YES PROBABLY NO LARGER (order of GB) YES YES
  63. SAVING STATE MECHANISM COMPREHENSIVE USAGE Bundle Store only a small

    information footprint that you need to rebuild the entire screen state from the other available means, usually information generated by user interactions (inputed texts, actual page from a large list, actual list position, etc) ViewModel Store related-and-processed data for screen, acessed previously from a single source of truth (Networking, Database, etc) behind a repository Local Storage Store data as soon you get it. Data format may not be UI-related at all, and can be adapted for the UI at ViewModel or Repository level
  64. CONCLUSIONS

  65. FINAL REMARKS Choose your components wisely! Do not misuse /

    overabuse ViewModels Use an Observer Pattern implementation to communicate between ViewModels and Activity / Fragments ( ) ViewModels should be a very tiny layer : leverage some presentation strategy to avoid fat classes!
  66. Architecture Components Docs https://developer.android.com/topic/libraries/architecture/ index.html Exploring the Architecure Components API

    https://goo.gl/WKE1sC ViewModels : Persistence, onSaveInstanceState( ), Restoring UI State and Loaders https://goo.gl/8iqdci
  67. ViewModels and LiveData - Patterns and Anti-Patterns https://goo.gl/nkVRYU Architecture Components

    Pitfalls https://goo.gl/GkBcbz Android LiveData QuickLook https://goo.gl/MqviKK Codelabs https://goo.gl/hbMZjy
  68. https://speakerdeck.com/ubiratansoares

  69. UBIRATAN SOARES Computer Scientist by ICMC/USP Software Engineer, curious guy

    Google Developer Expert for Android Teacher, speaker, etc, etc
  70. THANK YOU @ubiratanfsoares ubiratansoares.github.io https://br.linkedin.com/in/ubiratanfsoares