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

App development pragmatic best practices

rallat
November 02, 2017

App development pragmatic best practices

Video - https://www.youtube.com/watch?v=8ni8RY__WeU
A talk by Yigit Boyar (@yigitboyar) and Israel Ferrer Camacho (@rallat). In this talk, we want to share best practices to overcome those challenges every app has. We shared a survey asking over a 100 Android devs about their biggest challenges in Android app development (https://goo.gl/forms/QazA4OJUGzs7kCKj2). From analyzing the data from the survey, common challenges surfaced. First, we'll talk about best practices in app organization, architecture, and testing. After that we will be tackling some of Android's specific challenges like supporting OS versions, configuration changes, updating UI with live data (network, database), Fragments, concurrency and testing.

rallat

November 02, 2017
Tweet

More Decks by rallat

Other Decks in Programming

Transcript

  1. 61%

  2. Why Now? • It was always there, just popular now

    • Mature ecosystem • Bigger apps, bigger expectations
  3. Why Now? • It was always there, just popular now

    • Mature ecosystem • Bigger apps, bigger expectations • ART >>>> Dalvik
  4. Just Popular Now • Surely, writing Gmail was not easy

    • Works offline • With 32 mb memory
  5. Just Popular Now • Surely, writing Gmail was not easy

    • Works offline • With 32 mb memory • a screen of Pixel XL = 14 mb
  6. User Expectations works great on my EVO 4G is there

    any plans of adding the zoom functionality in the gmail app?
  7. User Expectations works great on my EVO 4G is there

    any plans of adding the zoom functionality in the gmail app? more and more emails are becoming HTML version now a days
  8. User Expectations works great on my EVO 4G is there

    any plans of adding the zoom functionality in the gmail app? more and more emails are becoming HTML version now a days and it would be really nice to have the ability to zoom in and out of them inside gmail
  9. User Expectations works great on my EVO 4G is there

    any plans of adding the zoom functionality in the gmail app? more and more emails are becoming HTML version now a days and it would be really nice to have the ability to zoom in and out of them inside gmail September 21, 2010 at 1:53 PM
  10. Mature Ecosystem • Need to write • networking code •

    image loading • view fragments • APK needs to be small
  11. Mature Ecosystem • Need to write • networking code •

    image loading • view fragments • APK needs to be small
  12. Mature Ecosystem • Need to write • networking code •

    image loading • view fragments • APK needs to be small • No Need to write
  13. Mature Ecosystem • Need to write • networking code •

    image loading • view fragments • APK needs to be small • No Need to write • retrofit
  14. Mature Ecosystem • Need to write • networking code •

    image loading • view fragments • APK needs to be small • No Need to write • retrofit • glide, picasso
  15. Mature Ecosystem • Need to write • networking code •

    image loading • view fragments • APK needs to be small • No Need to write • retrofit • glide, picasso • fragments, conductor
  16. Mature Ecosystem • Need to write • networking code •

    image loading • view fragments • APK needs to be small • No Need to write • retrofit • glide, picasso • fragments, conductor • split APKs etc
  17. Bigger Apps Bigger Expectations • 60 fps • lots of

    functionality • lots of integrations
  18. Bigger Apps Bigger Expectations • 60 fps • lots of

    functionality • lots of integrations • html everywhere
  19. Bigger Apps Bigger Expectations • 60 fps • lots of

    functionality • lots of integrations • html everywhere • works fully offline
  20. Bigger Apps Bigger Expectations • 60 fps • lots of

    functionality • lots of integrations • html everywhere • works fully offline • rich text editor
  21. Today RxJava Dagger MVP Reactive Cross Platform MVVM Clean Architecture

    Unit Testing Robolectric Data Binding Material Design Architecture Components Flutter React Native
  22. Separation of Concerns • Have it in day one, stick

    to it • Build architecture over time, as needed
  23. Think Like Recursion • A simple base case (or cases)—a

    terminating scenario that does not use recursion to produce an answer
  24. Think Like Recursion • A simple base case (or cases)—a

    terminating scenario that does not use recursion to produce an answer • A set of rules that reduce all other cases toward the base case
  25. Do Not Over-Engineer • Libraries, guides are tools not goals

    • The goal is to ship and maintain • Don’t use something just because X does
  26. Do Not Over-Engineer • Libraries, guides are tools not goals

    • The goal is to ship and maintain • Don’t use something just because X does • Their problem is not yours, neither their solution.
  27. Dependency Injection • Allows to declare dependencies of an object

    upfront. • This helps to easily mock dependencies and unit test the object
  28. Complex screens • Multiple logic layer components each with a

    single responsibility. • Logic layer components can subscribe to events tied to their logic (E.g. Rx, EventBus)
  29. Shared logic • E.g: Like a tweet happens in the

    timeline and the profile, tweet detail, DM, moments, pretty much everywhere.
  30. Shared logic • E.g: Like a tweet happens in the

    timeline and the profile, tweet detail, DM, moments, pretty much everywhere. • If the view is shared you can create a 3 layered complete feature.
  31. Shared logic • E.g: Like a tweet happens in the

    timeline and the profile, tweet detail, DM, moments, pretty much everywhere. • If the view is shared you can create a 3 layered complete feature. • If the view is not share then add another layer…
  32. Goal • Each layer has a reason to exist. •

    Consistency in codebase • Testable logic layer
  33. Goal • Each layer has a reason to exist. •

    Consistency in codebase • Testable logic layer • Reusable of logic layer
  34. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() }
  35. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onStart() { subscription = model.users.observe(adapter::setList) } }
  36. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onStart() { subscription = model.users.observe(adapter::setList) } fun onStop() { subscription?.cancel() } }
  37. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } }
  38. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { subscription = subscription ?: model.users.observe(adapter::setList) } }
  39. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { subscription = subscription ?: model.users.observe(adapter::setList) } fun onStop() { subscription?.cancel() } }
  40. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { subscription = subscription ?: model.users.observe(adapter::setList) } fun onStop() { subscription?.cancel() } }
  41. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { subscription = subscription ?: model.users.observe(adapter::setList) } fun onStop() { subscription?.cancel() } } might happen after onStop
  42. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { if (getLifecycle().isAtLeast(STARTED)) { subscription = subscription ?: model.users.observe(adapter::setList) } } fun onStop() { subscription?.cancel() } }
  43. class MyFragment : Fragment { var subscription : Subscription? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { if (getLifecycle().isAtLeast(STARTED)) { subscription = subscription ?: model.users.observe(adapter::setList) } } fun onStop() { subscription?.cancel() } } need to check current state
  44. class MyFragment : Fragment { val model by viewModel<MyViewModel>() val

    binding = // get binding instance fun onCreate() { binding.users = model.users } }
  45. class MyFragment : Fragment { val model by viewModel<MyViewModel>() val

    binding = // get binding instance fun onCreate() { button.onClick(this::loadUsers) } }
  46. class MyFragment : Fragment { val model by viewModel<MyViewModel>() val

    binding = // get binding instance fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { binding.users = model.users } }
  47. class MyFragment : Fragment { val model by viewModel<MyViewModel>() fun

    onCreate() { model.users.observe(this, adapter::setList) } }
  48. class MyFragment : Fragment { val model by viewModel<MyViewModel>() fun

    onCreate() { model.users.observe(this, adapter::setList) } }
  49. class MyFragment : Fragment { val model by viewModel<MyViewModel>() fun

    onCreate() { model.users.observe(this, adapter::setList) } }
  50. class MyFragment : Fragment { var subscription : Unit? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } }
  51. class MyFragment : Fragment { var subscription : Unit? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } }
  52. class MyFragment : Fragment { var subscription : Unit? =

    null val model by viewModel<MyViewModel>() fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { subscription = subscription ?: model.users.observe(this, adapter::setList) } }
  53. class MyFragment : Fragment { val model by viewModel<MyViewModel>() fun

    onCreate() { model.users .autoDisposeWith(this) .subscribe(adapter::setList) } }
  54. class MyFragment : Fragment { val model by viewModel<MyViewModel>() fun

    onCreate() { model.users .autoDisposeWith(this) .subscribe(adapter::setList) } }
  55. class MyFragment : Fragment { val model by viewModel<MyViewModel>() fun

    onCreate() { model.users .autoDisposeWith(this) .subscribe(adapter::setList) } }
  56. class MyFragment : Fragment { val model by viewModel<MyViewModel>() var

    disposable : Disposable? = null fun onCreate() { button.onClick(this::loadUsers) } }
  57. class MyFragment : Fragment { val model by viewModel<MyViewModel>() var

    disposable : Disposable? = null fun onCreate() { button.onClick(this::loadUsers) } }
  58. class MyFragment : Fragment { val model by viewModel<MyViewModel>() var

    disposable : Disposable? = null fun onCreate() { button.onClick(this::loadUsers) } fun loadUsers() { disposable = disposable ?: model .autoDisposeWith(this) .subscribe(adapter::setList) } }
  59. Config Change • Decouple UI and data • Use ViewModel

    App Restart • Drive UI from disk • Use savedInstanceState
  60. App Restart • Nothing in memory survives :( • NBU

    was one of the top problems in the survey
  61. App Restart Saved State Disk • View’s information (e.g. RecyclerView

    position) • UI’s arguments • Application data
  62. Screen fragmentation • Flexible UIs with ConstraintLayout (9+) • Create

    custom layouts for specific size/densities • Use wrap_content, match_parent • Density independent units (dp, sp) • VectorDrawables (limitations)
  63. Hardware Fragmentation • Generate a class that defines hardware profile

    - low, medium, high depending on: RAM and CPU cores and frequency. • Yearclass lib foundation to classify hardware profiles https://github.com/facebook/device-year-class
  64. interface Animator { fun run() } class AnimatorFactory { fun

    animatorForProfile(profile: HardwareProfile): Animator = when (profile) { HardwareProfile.HIGH -> AnimatorHighPerf() HardwareProfile.MEDIUM -> AnimatorMediumPerf() else -> object : Animator { override fun run() { } } } } // and then val animator = AnimatorFactory().animatorForProfile(applicationContext.hardwareProfiler) animator.run()
  65. RxTextView .textChanges(name) .debounce(350, TimeUnit.MILLISECONDS) .filter(validateUsername()) .flatMap(text -> api.sendToServer(text)) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(Schedulers.io())

    .subscribe(handleResponse, handleError); FluentApi Reactive push Easy case handling Easy and Declarative Threading Useful built-in operators
  66. Warnings • Rx and Dagger help to scale your app.

    • It comes at a cost. Learning their DSL, idioms. The team needs to learn it and grow criteria for code reviews. Exception: your name is Jake or Ron and you work at Google. • Moreover, many new hires will need to learn Android and these new idioms all at once.
  67. • Does Android provide it? • Broadly abroad open-source option?

    • Can you find a simpler approach? • Ask your coworkers or industry peers to know if makes sense. Sometimes we have great ideas in our caves, but they aren’t so much.
  68. Warning • When you commit to build your own framework,

    you are the one responsible for maintain it. • New comers to the code base will have to learn it.
  69. Rule of Tree • By Don Roberts • 1st time

    • just do it! • 2nd time • ok to copy
  70. Rule of Tree • By Don Roberts • 1st time

    • just do it! • 2nd time • ok to copy • 3rd time • Now you refactor
  71. Refactor Tips • You touch it, you fix it! •

    Refactor a file when it is touched
  72. Refactor Tips • You touch it, you fix it! •

    Refactor a file when it is touched • Separate the refactor CL from the original
  73. Refactor Tips • Testing is a must! • If there

    are tests, make sure they pass
  74. Refactor Tips • Testing is a must! • If there

    are tests, make sure they pass • If there are no tests, first write them