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

Daniel Gallego Vico

July 15, 2016
Tweet

More Decks by Daniel Gallego Vico

Other Decks in Programming

Transcript

  1. Daniel Gallego Vico - Android Lead
    [email protected]
    @thanos_malkav
    The Pragmatic
    Android Programmer
    from hype to reality

    View Slide

  2. Motivation of this talk
    Previously on Android…

    View Slide

  3. Motivation of this talk
    CLEAN
    SOLID
    ReactiveX
    MVP
    MVVM
    Hexagonal
    Testing
    CI
    TDD
    BDD
    Dependency Injection
    Flux
    CD
    Automation
    Now on Android…
    Kotlin

    View Slide

  4. Motivation of this talk

    View Slide

  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

    View Slide

  6. Pragmatic CLEAN
    Architecture

    View Slide

  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

    View Slide

  8. How: pragmatic clean architecture
    View
    View
    Interface
    Presenter
    Presenter
    Interface
    Interactor
    Interface
    Interactor
    Controller
    Wireframe
    Wireframe
    Interface
    Controller
    Interface
    Android
    Java

    View Slide

  9. How: package structure
    shared (pure Java module) Apps (Android module)

    View Slide

  10. Trick: bind Views & Wireframes to Presenters
    public interface BasePresenter {


    /** 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();

    }

    View Slide

  11. Trick: bind Views & Wireframes to Presenters
    public abstract class BaseCleanActivity> 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;

    }

    }

    View Slide

  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

    View Slide

  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

    View Slide

  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();
    ...

    }

    View Slide

  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() {

    @NotNull

    @Override

    public BTConnectionControllerImpl get() {

    return new BTConnectionControllerImpl(
    application.getApplicationContext(),

    BluetoothAdapter.getDefaultAdapter(),
    provideSessionController(),
    provideUiScheduler());

    }

    });

    }
    ...
    }

    View Slide

  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();

    View Slide

  17. How: Rx Java + CLEAN
    View
    View
    Interface
    Presenter
    Presenter
    Interface
    Interactor
    Interface
    Interactor
    Controller
    Wireframe
    Wireframe
    Interface
    Controller
    Interface
    Android
    Java

    View Slide

  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

    View Slide

  19. Pragmatic Testing

    View Slide

  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%


    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  24. How: integration tests
    • Controller
    • Tests usage of Android APIs responsible of informing about
    device free storage in bytes
    • Using Robolectric
    • Shadow storage API

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  28. Pragmatic Continuous
    Development/Testing/Integration/Delivery

    View Slide

  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

    View Slide

  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-
    • or /values in case of the default lang
    • Available on GitHub!

    View Slide

  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

    View Slide

  32. How: assets automation
    • Automatic graphic resources integration
    • Designers push resources to a suitable branch
    • The branch is merged periodically
    • Forget about Dropbox, Drive…

    View Slide

  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

    View Slide

  34. How: pull-request workflow
    pull-request to develop
    Build
    Test
    Report
    See
    Stash pull request build plugin
    Polling
    Comment
    result

    View Slide

  35. Ninjas, ninjas everywhere…
    But sometimes a ninja commit could be necessary/useful

    View Slide

  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: +

    View Slide

  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!

    View Slide

  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

    View Slide

  39. How: project dashboard
    izan.moreno

    View Slide

  40. How: project dashboard
    • By monitoring Jira we know projects status
    • Stories, improvements, tasks & bugs
    • To do, in progress & blocked

    View Slide

  41. How: project dashboard

    View Slide

  42. How: project dashboard
    • By monitoring Bugsnag we know the existing
    problems:
    • Errors
    • Warnings
    • Infos

    View Slide

  43. How: project dashboard

    View Slide

  44. How: project dashboard
    • By monitoring Google Play we know
    • Latest user reviews and ratings
    • Average apps rating
    • [4, 5]
    • [3, 4)
    • [1, 3)

    View Slide

  45. How: project dashboard

    View Slide

  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

    View Slide

  47. Final thoughts

    View Slide

  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

    View Slide

  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

    View Slide

  50. The key to success is a great team

    View Slide

  51. Daniel Gallego Vico - Android Lead
    [email protected]
    @thanos_malkav
    Namaste!

    View Slide