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. 3.

    Motivation of this talk CLEAN SOLID ReactiveX MVP MVVM Hexagonal

    Testing CI TDD BDD Dependency Injection Flux CD Automation Now on Android… Kotlin
  2. 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
  3. 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
  4. 8.

    How: pragmatic clean architecture View View Interface Presenter Presenter Interface

    Interactor Interface Interactor Controller Wireframe Wireframe Interface Controller Interface Android Java
  5. 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();
 }
  6. 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;
 }
 }
  7. 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
  8. 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
  9. 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(); ...
 }
  10. 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());
 }
 });
 } ... }
  11. 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();
  12. 17.

    How: Rx Java + CLEAN View View Interface Presenter Presenter

    Interface Interactor Interface Interactor Controller Wireframe Wireframe Interface Controller Interface Android Java
  13. 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
  14. 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%

  15. 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
  16. 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
  17. 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
  18. 24.

    How: integration tests • Controller • Tests usage of Android

    APIs responsible of informing about device free storage in bytes • Using Robolectric • Shadow storage API
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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!
  24. 32.

    How: assets automation • Automatic graphic resources integration • Designers

    push resources to a suitable branch • The branch is merged periodically • Forget about Dropbox, Drive…
  25. 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
  26. 34.

    How: pull-request workflow pull-request to develop Build Test Report See

    Stash pull request build plugin Polling Comment result
  27. 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: +
  28. 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!
  29. 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
  30. 40.

    How: project dashboard • By monitoring Jira we know projects

    status • Stories, improvements, tasks & bugs • To do, in progress & blocked
  31. 42.

    How: project dashboard • By monitoring Bugsnag we know the

    existing problems: • Errors • Warnings • Infos
  32. 44.

    How: project dashboard • By monitoring Google Play we know

    • Latest user reviews and ratings • Average apps rating • [4, 5] • [3, 4) • [1, 3)
  33. 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
  34. 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
  35. 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