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

Droidcon Spain 2016 - The Pragmatic Android Programmer: from hype to reality

Droidcon Spain 2016 - The Pragmatic Android Programmer: from hype to reality

Watch the presentation: https://youtu.be/oU9zJ9yof1I

This presentation shows how to be a pragmatic Android programmer by showing real examples of applications/products developed in BQ attending to three important topics:

- Why and how to implement a pragmatic CLEAN architecture with a custom dependency injection framework and ReactiveX features.

- Why and how to implement unit, integration, view and smoke tests in every CLEAN layer and what tools should be used.

- Why and how to implement pragmatic Continuous
Development/Testing/Integration/Delivery by showing several tricks, plugins and snippets that you could use as a daily basis.

Links to open source released for this talk:
- POedtior Android Gradle Plugin: https://github.com/bq/poeditor-android-gradle-plugin/
- Tag-flow (automatic commit tagging): https://github.com/bq/tag-flow
Dashboard widgets based on Dashing.io
- Bugsnag Error: http://bit.ly/29LnMw2
- Google Play Reviews: http://bit.ly/2a0wNmG
- Google Play Rating: http://bit.ly/29z8aJO

4143a69d2d7722f3f687c097c70c4613?s=128

Daniel Gallego Vico

July 15, 2016
Tweet

Transcript

  1. Daniel Gallego Vico - Android Lead daniel.gallego@bq.com @thanos_malkav The Pragmatic

    Android Programmer from hype to reality
  2. Motivation of this talk Previously on Android…

  3. Motivation of this talk CLEAN SOLID ReactiveX MVP MVVM Hexagonal

    Testing CI TDD BDD Dependency Injection Flux CD Automation Now on Android… Kotlin
  4. Motivation of this talk

  5. My goal here: understand why before how • Do not

    apply concepts you do not understand/need • I will explain why during 2015 we decided to apply most of the previous concepts and how we did it: 1. Pragmatic CLEAN architecture 2. Pragmatic testing 3. Pragmatic continuous Development/Testing/Integration/ Delivery
  6. Pragmatic CLEAN Architecture

  7. Why • Several projects in one year • All of

    them from scratch • We need maintainable/testable/decoupled/ easy to evolve projects • Small team (6 developers) • Impossible to work only in one project • MUST change between projects smoothly
  8. How: pragmatic clean architecture View View Interface Presenter Presenter Interface

    Interactor Interface Interactor Controller Wireframe Wireframe Interface Controller Interface Android Java
  9. How: package structure shared (pure Java module) Apps (Android module)

  10. Trick: bind Views & Wireframes to Presenters public interface BasePresenter<V,

    W> {
 
 /** Called when the bound view is created. getView() will return a valid, active view from this point, * point, so it is safe to start subscriptions or any other code depending on getView() */ 
 void onCreateView();
 
 /** Called when the bound view is about to be destroyed and unbound.
 * getView() will return null from this point, so every subscription
 * or any other code depending on getView() should be unsubscribed/managed. */
 void onDestroyView();
 
 /**
 * Needed to create the relation between View-Presenter-Wireframe.
 * @param view an active view of the needed type.
 * @param wireframe an initialized wireframe of the needed type.
 */
 void bindViewAndWireframe(V view, W wireframe);
 
 /** Called after onDestroyView. Should perform clean-up of variables pointing to the view. */
 void unBindViewAndWireframe();
 
 /** @return the bound view. May be null if the view was unbound. */
 V getView();
 
 /** @return the bound Wireframe. */
 W getWireframe();
 
 /** Attach the subscription to the view lifecycle, and unsubscribe when it goes out of scope. /*
 void track(Subscription subscription);
 
 /** Unsubscribe all tracked subscriptions. */
 void cancelSubscriptions();
 }
  11. Trick: bind Views & Wireframes to Presenters public abstract class

    BaseCleanActivity<T extends BasePresenter<?, ?>> extends BaseActivity {
 private T presenter;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 // Retrieve presenter instance
 this.presenter = resolvePresenter();
 }
 
 @Override
 protected void onPostCreate(Bundle savedInstanceState) {
 super.onPostCreate(savedInstanceState);
 this.presenter.onCreateView();
 }
 
 protected abstract T resolvePresenter();
 
 @Override
 protected void onDestroy() {
 super.onDestroy();
 getPresenter().onDestroyView();
 getPresenter().unBindViewAndWireframe();
 }
 
 protected T getPresenter() {
 if (presenter.getView() != this) throw new IllegalArgumentException("Not bound!");
 return presenter;
 }
 }
  12. Trick: solving orientation changes • Views use onSaveInstanceState • Stateless

    Presenters are recreated • State is recovered from network cache + database • Single data retrieving flow • High simplification of code vs Resource consumption
  13. How: dependency injection • Homemade solution • Dagger 1 was

    too complicated (still no Dagger 2 release) • The team needed to understand the benefits and how to use it • Easier than expected • Implementation: • Shared: DependencyInjector + DependencyCache • App: AndroidDependencyInjector
  14. How: dependency injection public abstract class DependencyInjector { ... public

    abstract Scheduler provideUiScheduler(); public CalibrationPresenter provideCalibrationPresenter() {
 return new CalibrationPresenterImpl(
 provideConnectToZowiInteractor(),
 provideMeasureZowiBatteryLevelInteractor(),
 provideCheckInstalledZowiAppInteractor(),
 provideSendAppToZowiInteractor(), provideSendCommandToZowiInteractor(),
 provideFactoryFirmwarePath(),
 provideUiScheduler());
 } public MeasureZowiBatteryLevelInteractor provideMeasureZowiBatteryLevelInteractor() {
 return new MeasureZowiBatteryLevelInteractorImpl( provideSendCommandToZowiInteractor(),
 provideZowiDataController()); } public abstract BTConnectionController provideBTConnectionController(); ...
 }
  15. How: dependency injection public class AndroidDependencyInjector extends DependencyInjector { ...

    @Override
 public Scheduler provideUiScheduler() {
 return AndroidSchedulers.mainThread();
 } @Override
 public BTConnectionController provideBTConnectionController() {
 return getCache().get(BTConnectionControllerImpl.class, new DependencyCache.Provider<BTConnectionControllerImpl>() {
 @NotNull
 @Override
 public BTConnectionControllerImpl get() {
 return new BTConnectionControllerImpl( application.getApplicationContext(),
 BluetoothAdapter.getDefaultAdapter(), provideSessionController(), provideUiScheduler());
 }
 });
 } ... }
  16. Why: Rx Java • Easy to manage complex communications •

    Great for projects involving HW • Make thread management easier • Beware of concurrency problems: they do not disappear! • Improve team development speed… • … once the initial learning curve is overcome • RxAndroid only for AndroidSchedulers.mainThread();
  17. How: Rx Java + CLEAN View View Interface Presenter Presenter

    Interface Interactor Interface Interactor Controller Wireframe Wireframe Interface Controller Interface Android Java
  18. Future steps • Package structure refactoring • Order by feature,

    not CLEAN layer (avoid useless classes/interfaces) • Improves maintainability and decouple features • Shared (pure Java) module will disappear • But will remain in every feature-package • Dagger 2 • Too much boilerplate to maintain a homemade solution nowadays • Improves visibility: we do not need everything public • Helps testing
  19. Pragmatic Testing

  20. Why • Small QA team: • With a great functional

    framework • But still very dependent of manual testing • Unit and integration tests were needed to: • Automate everything that can be automatically tested • Spend QA team time in engineering tasks -> mental health • Ensure quality before testing the app as a user will do • Avoid the “Great certification” phenomenon (aka Ping-Pong) • Keep crash rate under 1%

  21. How: use case • For “device storage is full” feature,

    we will show • What is tested • How it is done • In every CLEAN layer Click on book cover Manage click to download book Tries to download or return typed error Computes available space against file size Asks system about storage available View Presenter Interactor Storage Controller Device Controller
  22. How we tested this before • QA people fill a

    device storage using adb • Create a user with suitable context properties • Able to download a book • With a heavy book in the library • Etc. • Try to download the book manually • Check that “No space message” error is shown
  23. How: unit tests • Interactor: • Tests method in charge

    of returning NO_SPACE error when the device does not have enough space for download • Mocking controllers • Controller: • Tests method in charge of deciding whether or not the device has enough space to download a file • Mocking system • Using JUnit & Mockito
  24. How: integration tests • Controller • Tests usage of Android

    APIs responsible of informing about device free storage in bytes • Using Robolectric • Shadow storage API
  25. How: view tests • View + Presenter • Tests that

    a Dialog is shown to the user in case the device does not have enough space to download a file • Validates the interaction between view and presenter • Do not test them separately! • Using Mockito & Espresso • Mocking interactors
  26. How: smoke tests • Backend API • Tests request that

    informs the app about the file size to be downloaded • Using JUnit & Robolectric • Working with Interactors/Controllers • Useful for quick reporting of backend errors & breaking changes
  27. Future steps • Improve current testing frameworks • E.g. Analytics

    -> tests screen tracking • Saves 2 hours of manually testing per certification • Increase coverage • Apply robot pattern • To encode high level actions that takes place on a view in order to use them in many tests
  28. Pragmatic Continuous Development/Testing/Integration/Delivery

  29. • The repository must be a sanctuary • Git-flow to

    avoid errors and allow hooks • Everything is automated • The less human intervention, the better • Ensure that every pull-request passes the tests • Otherwise, fail fast • Easy daily distribution of APKs • Real-time awareness of project state Why KEEP CALM AND AUTOMATE
  30. How: automatic localization • POeditor Android Gradle Plugin • Allows

    single multi-platform string definition • Download all strings files (every available lang) from PoEditor • Process incoming strings to fix some PoEditor incompatibilities with Android strings system • Create strings.xml files to /values-<lang> • or /values in case of the default lang • Available on GitHub!
  31. How: automatic localization flow /values/strings.xml /values-sw600dp/strings.xml /values-es/strings.xml /values-es-sw600dp/strings.xml UX multi-platform

    strings POeditor Android Gradle Plugin Marketing Translation team
  32. How: assets automation • Automatic graphic resources integration • Designers

    push resources to a suitable branch • The branch is merged periodically • Forget about Dropbox, Drive…
  33. How: automate version code & name /**
 * Gets the

    version name from the latest Git tag. E.g. tag v.3.0.1.2 -> versionName 3.0.1.2
 */
 def computeVersionName() {
 def stdout = new ByteArrayOutputStream()
 exec {
 commandLine 'git', 'describe', '--abbrev=0', '--tags'
 standardOutput = stdout
 }
 return stdout.toString().trim().replaceAll('v', '')
 }
 
 /**
 * Gets the commit number from the current Git commit. E.g. 538
 */
 def computeVersionCode() {
 def stdout = new ByteArrayOutputStream()
 exec {
 commandLine 'git', 'rev-list', '--count', 'HEAD'
 standardOutput = stdout
 }
 return stdout.toString().trim().toInteger()
 } defaultConfig { versionName computeVersionName()
 versionCode computeVersionCode() ... } Uses tag-flow Increases snapshot version only when HEAD is not tagged
  34. How: pull-request workflow pull-request to develop Build Test Report See

    Stash pull request build plugin Polling Comment result
  35. Ninjas, ninjas everywhere… But sometimes a ninja commit could be

    necessary/useful
  36. How: daily snapshot workflow tag-flow assemble git clone /develop importGraphicResources

    importPoEditorStrings computeVersionCode computeVersionName build minified + sign test APK publication (maven-publish) test results publication Notify release: +
  37. How: app delivery • Daily snapshots of all necessary product

    flavors • Homemade RepoApp allows coworkers to download the latest version with one click • Maven repository is hidden to make things easier • Weekly delivery to Google Play alpha users • Provides real feedback from real users = free manual testing • Don’t worry about crashes! • Google Play beta used to test RCs • A promote to production!
  38. How: project dashboard • By monitoring Jenkins reports we know

    when • A test has failed • A build is broken • And enjoy the pleasure of blaming
  39. How: project dashboard izan.moreno

  40. How: project dashboard • By monitoring Jira we know projects

    status • Stories, improvements, tasks & bugs • To do, in progress & blocked
  41. How: project dashboard

  42. How: project dashboard • By monitoring Bugsnag we know the

    existing problems: • Errors • Warnings • Infos
  43. How: project dashboard

  44. How: project dashboard • By monitoring Google Play we know

    • Latest user reviews and ratings • Average apps rating • [4, 5] • [3, 4) • [1, 3)
  45. How: project dashboard

  46. Future steps • SonarQube for static code analysis • Performance

    analysis • Update ticket status with build number after successful pull requests • Ready for QA • Google Play REST API to automate publication
  47. Final thoughts

  48. Common sense • Do not be afraid of improving when

    your context and necessities demands it • But please, do not be reckless when more people are affected by your decisions • And remember: hype is the enemy of reality
  49. BQ Open Source • https://github.com/bq/poeditor-android-gradle-plugin/ • https://github.com/bq/tag-flow • Dashboard widgets

    based on Dashing.io • Bugsnag Error: http://bit.ly/29LnMw2 • Google Play Reviews: http://bit.ly/2a0wNmG • Google Play Rating: http://bit.ly/29z8aJO
  50. The key to success is a great team

  51. Daniel Gallego Vico - Android Lead daniel.gallego@bq.com @thanos_malkav Namaste!