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

Effective mobile engineering to make product successful

Effective mobile engineering to make product successful

Have you ever wondered how the best product ideas succeed? It's not only great timing, product-market fit or marketing activities. It's also about business and engineering cooperation.

In this presentation, I'll talk about our experiences with making great (and bad) ideas happen. How to deliver fast, learn from mistakes and keep your users happy (and crash free!).

I will show how the team of a few engineers can build the app used globally. How we develop and maintain our code, test, deliver, learn and iterate.

Mirosław Stanek

October 05, 2018
Tweet

More Decks by Mirosław Stanek

Other Decks in Programming

Transcript

  1. View Slide

  2. fail fast

    View Slide

  3. Anyone who has never made a mistake
    has never tried anything new.
    -Albert Einstein

    View Slide

  4. IT BIZ

    Me

    View Slide

  5. Fail fast,
    Fail often,
    Fail better,
    Succeed

    View Slide

  6. Ship fast,
    Iterate often,
    Don’t crash,
    Succeed
    Fail fast,
    Fail often,
    Fail better

    View Slide

  7. #
    KPIs

    View Slide

  8. Crash-free users >99.5%
    Release once a week

    View Slide

  9. New app once a week???

    View Slide

  10. Release
    Once a month
    x1 x2 x5 x6
    x3 x4 x7 x8
    team’s capacity

    View Slide

  11. x1 x2 x3 x4
    x5 x6 x7 x8 build 1 checking
    Dev QA/Business
    build1
    build2
    Week 1
    Week 2
    Week 3
    “W
    hat’s wrong,
    W
    hat’s ok?”
    “W
    hat’s wrong,
    W
    hat’s ok?”
    build 2 checking

    View Slide

  12. x1 x2 x3 x4
    x5 x6 x7 x8 build 1 testing
    x1’ x2’ x4’ build 2 testing
    Dev QA/Business
    build1
    build2
    Week 1
    Week 2
    Week 3
    “we don’t like
    x5, x8”
    “we don’t like
    x1, x2, x4”
    x5’ x8’
    Week 4

    View Slide

  13. x1 x2 x3 x4
    x5 x6 x7 x8 build 1 testing
    x1’ x2’ x4’ build 2 testing
    Dev QA/Business
    build1
    build2
    build3
    Week 1
    Week 2
    Week 3
    fixes
    x5’ x8’ x1’ x2’ x4'
    Week 4
    more fixes
    “checking them now..”
    x5’ x8’
    RELEASE!!!

    View Slide

  14. x1 x2 y2 z1
    x3 y1 z2 z3
    team’s capacity (same as previous)
    Release
    Once a week

    View Slide

  15. Week 1 x1 x2 x1’ x2’ x1 x2 x1’
    Dev QA/Business

    View Slide

  16. x1’ x2’ x1’
    build 1
    build 2
    Week 1 x1 x2 x1 x2
    Dev QA/Business

    View Slide

  17. x1’
    feedback 1
    feedback 2
    Week 1 x1 x2 x1’ x2’ x1 x2
    Dev QA/Business

    View Slide

  18. Week 1 x1 x2 x1’ x2’
    Week 2 x3
    x1 x2 x1’
    Dev QA/Business
    x2’ x3
    build 4
    build 5
    release candidate v1
    RELEASE!

    View Slide

  19. Week 1 x1 x2 x1’ x2’
    Week 2 x3 y1 y2
    x1 x2 x1’
    Dev QA/Business
    x2’ y1
    x3
    feedback 1
    build
    Week 3

    View Slide

  20. Week 1 x1 x2 x1’ x2’
    Week 2 x3 y1 y2
    x1 x2 x1’
    Dev QA/Business
    y1’ z1 z2
    Week 3
    x2’ y1
    y1’ z1
    y2
    x3
    v2
    RELEASE!

    View Slide

  21. Week 1 x1 x2 x1’ x2’
    Week 2 x3 y1 y2
    x1 x2 x1’
    Dev QA/Business
    y1’ z1 z2
    Week 3
    x2’ y1
    y1’ z1
    y2
    x3
    z3
    v3
    RELEASE!
    Week 4 z1’ z2’ z3 z1’ z2’
    z2

    View Slide

  22. x1 x2 y2 z1
    x3 y1 z2 z3
    x1 x2 x5 x6
    x3 x4 x7 x8
    vs

    View Slide

  23. x1 x2 y2 z1
    x3 y1 z2 z3
    x1 x2 x5 x6
    x3 x4 x7 x8
    • Same context, reasonable overhead
    • Different context, a lot to keep in mind
    How many things to have in mind by
    engineers
    Software 

    engineer
    focus
    QA 

    engineer
    focus
    QA and software

    engineer
    focus

    View Slide

  24. How many things can go wrong
    x1 x2 y2 z1
    x3 y1 z2 z3
    x1 x2 x5 x6
    x3 x4 x7 x8
    • Same context, reasonable overhead
    • less things can break
    • Different context, a lot to keep in mind
    • more things can break

    View Slide

  25. Business starts learning from
    here
    x1 x2 y2 z1
    x3 y1 z2 z3
    x1 x2 x5 x6
    x3 x4 x7 x8
    • Same context, reasonable overhead
    • less things can break
    • business can iterate fast
    • Different context, a lot to keep in mind
    • more things can break
    • business can’t iterate fast
    “We’ve learned that
    you don’t have to build this”
    “We’ve learned that
    you didn’t have to build this”

    View Slide


  26. SHIP FAST

    View Slide

  27. ?
    x2
    x3
    x1

    View Slide

  28. Coding
    Testing
    and
    review
    x2
    x3
    x1
    Release
    candidate

    View Slide


  29. ???
    Coding
    Testing
    and
    review
    x2
    x3
    x1
    Release
    candidate

    View Slide

  30. Manual
    tests
    Others
    What’s
    new
    Build
    apk
    Internal
    distrib
    Internal build
    Coding
    Testing
    and
    review
    x2
    x3
    x1
    Release
    candidate

    View Slide

  31. Sign

    apk
    Release

    notes
    Internal

    distrib
    Update
    assets
    Store

    upload
    RC distribution
    Coding
    Testing
    and
    review
    x2
    x3
    x1
    Release
    candidate

    View Slide

  32. +2h
    x number of loops
    +2h
    Coding
    Testing
    and
    review
    x2
    x3
    x1
    Release
    candidate
    Internal build RC distrib
    x
    Rollout
    +1-2 days

    View Slide

  33. +2h
    Coding
    Testing
    and
    review
    x2
    x3
    x1
    Release
    candidate
    Internal build RC distrib
    x
    Rollout
    +1-2 days
    +2h
    x number of loops

    View Slide

  34. clean the
    project
    unit tests
    tests
    coverage
    lint
    checks
    assemble
    apk
    distribute notify
    INTERNAL BUILD
    Coding
    Testing
    and
    review

    View Slide

  35. INTERNAL BUILD
    $ ./gradlew
    clean
    testDebugUnitTest
    testDebugUnitTestCoverage
    lintStaging
    assembleStaging
    crashlyticsUploadDistributionStaging
    clean the
    project
    unit tests
    tests
    coverage
    lint
    checks
    assemble
    apk
    distribute notify
    pipeline {
    agent { node { label 'mobile' }}
    //...
    stages {
    stage("Build and distribute debug") {
    steps {
    }
    }
    }
    }

    View Slide

  36. New
    feature
    branch
    On
    demand
    Create
    release
    branch
    Build and
    distribute
    RC
    Create
    Merge Request
    Notify
    reviewer
    New 

    release candidate
    Create
    merge
    request
    Bump
    version
    code
    4 clicks
    10 clicks
    3 lines of
    code
    Push to
    master
    Push to
    Play Store
    beta
    Production
    app quick
    tests
    Store release
    Build,
    check,
    distribute
    apk
    30min of
    clicking
    Trigger
    Actions
    Savings

    View Slide

  37. Run all
    tests
    Create
    report
    Send
    report to
    slack
    Nightly
    tests
    Setup and
    run
    simulators
    Every
    24hrs

    View Slide

  38. Every
    24hrs
    Update 

    /res/values
    Remove
    unused
    resources
    create
    merge
    request
    Update
    translations
    Fetch
    translation
    sfrom API
    lint checks

    View Slide

  39. apply plugin: 'io.fabric'
    apply plugin: 'jacoco'
    apply plugin: 'com.getkeepsafe.dexcount'
    apply plugin: 'com.github.triplet.play'
    def process_language(language):
    print('Starting processing of: ' + language)
    is_default_language = language == ApiConfig.DEFAULT_LANGUAGE
    translations = translations_api.get_translations()
    if '--android' in sys.argv:
    parsed_keys = transform_all_translations(translations)
    android_exporter = ATranslationsExporter(parsed_keys, lang_code)
    android_exporter.generate_translations()
    if is_default_language:
    android_exporter.cleanup_android_new_translations()
    pipeline {
    triggers {
    gitlab(triggerOnPush: true, branchFilterType: 'All')
    }
    stage("Build debug") {
    when { branch 'dev' }
    steps { sh './gradlew clean assembleDebug --stacktrace' }
    }
    }

    View Slide

  40. Coding
    Testing
    and
    review
    x2
    x3
    x1
    Release
    candidate
    Internal build RC distrib
    auto
    x
    Rollout
    auto
    +1-2 days

    View Slide

  41. *
    QUALITY
    ASSURANCE

    View Slide


  42. Key feature can be tested manually
    only once.

    View Slide

  43. RC QA
    fast feedback QA
    Development
    3 STAGES OF QA
    Unit tests
    Lint checks
    Manual tests
    Functional
    tests
    End-to-end
    tests
    Test infra
    maintenance

    View Slide

  44. LINT CHECKS

    View Slide

  45. UNIT
    TESTING

    View Slide

  46. Written by devs -
    Test single units
    100% of tests must pass ✅

    View Slide

  47. @Test

    public void testInit_whenConditionsMet_thenShouldShowRatingBox() {

    when(rateBoxManager.shouldAskForRate()).thenReturn(true);

    activityPresenter.initView();
    verify(activityMock, once()).showRateBox();
    }
    >200sec to
    find this
    screen/popup
    <1sec to
    execute test

    View Slide

  48. @Test

    public void testCriteria_whenAllMet_thenShouldShowRateBox() {

    when(appDataMock.getAppInstallTime()).thenReturn(DATE_7_DAYS_AGO);

    when(rateBoxReminderMock.isDelayed()).thenReturn(false);
    assertEquals(rateBoxManager.shouldAskForAppRate(), true);

    }
    @Test

    public void testCriteria_whenAppFreshlyInstalled_thenShouldntShowRateBox() {
    when(appDataMock.getAppInstallTime()).thenReturn(DATE_TODAY);

    when(rateBoxReminderMock.isDelayed()).thenReturn(true);

    assertEquals(rateBoxManager.shouldAskForAppRate(), false);

    }
    CRITERIA UNIT TESTS

    View Slide


  49. COVERAGE 100% (?)

    View Slide

  50. @Test

    public void testSetGet() {

    assertFalse(model.isSet());
    model.setItTo(true);

    assertTrue(model.isSet());
    model.setItTo(false);
    assertFalse(model.isSet());

    }
    COVERAGE 100%
    @Test

    public void testConstructor() {

    model = new Model(true);

    assertTrue(model.isSet());

    }

    View Slide

  51. 2 unit tests,
    0 integration tests

    View Slide

  52. COVERAGE 50-60%
    & FAST FORWARD TO QA
    COVERAGE 100%

    View Slide

  53. INTEGRATION
    TESTING

    View Slide

  54. Built by QA engineers
    Run on emulator
    Hermetic env

    View Slide

  55. Data and settings
    Use cases,
    presentation logic

    View Slide

  56. mocks, init config System under test
    Data and settings
    Use cases,
    presentation logic

    View Slide

  57. mocks System under test
    Use cases,
    presentation logic

    View Slide

  58. mocks
    Screen by
    screen flow:
    - Splash
    - Login
    - Main Screen
    - List if items
    API calls
    +
    database
    operations
    Screen by
    screen flow:
    - Splash
    - Login
    - Main Screen
    - List if items
    API calls
    +
    database
    operations
    5s
    5s 5s
    0s

    View Slide

  59. Android instrumentation test template
    AVD
    manager
    System under
    testing

    View Slide

  60. Android instrumentation test template
    Fake data
    (e.g. json file)
    AVD
    manager
    System under
    testing
    Mock objects

    View Slide

  61. Android instrumentation test template
    Init state
    Activity
    System under
    testing
    Fake data
    (e.g. json file)
    AVD
    manager
    Mock objects

    View Slide

  62. @RunWith(AndroidJUnit4.class)
    public class ForceAppUpdate_MockTests extends AzimoTestContainer {
    @Rule public DaggerMockRule daggerRule = DaggerMockUtils.defaultDaggerMockRule();
    @Mock ApiVersionChecker apiVersionChecker;
    public void setupLogic() {
    when(apiVersionChecker.shouldUpdateApplication()).thenReturn(true);
    }
    @Test
    public void testForceAppUpdate_ifAppIsTooOld_shouldShowUpdateAzimoScreen() throws Exception {
    /* Data mocking (App-State unrelated) */
    when(apiVersionChecker.shouldUpdateApplication()).thenReturn(true);
    /* Config init */
    TestLauncher.init()
    .setting_setInputToUser(TestUsers.getTestUserNoTransfersNoRecipients())
    .setting_useStartingPoint(LauncherStartingCondition.USER)
    .run();
    /* Test Start */
    appTooOldNavi().waitForEnteredActivity(AppTooOldActivity.class);
    appTooOldAsserts().assertThat_correctTitleIsDisplayed();
    appTooOldAsserts().assertThat_correctDescriptionIsDisplayed();
    appTooOldAsserts().assertThat_correctButtonDescriptionIsDisplayed();
    }
    }

    View Slide

  63. @RunWith(AndroidJUnit4.class)
    public class ForceAppUpdate_MockTests extends AzimoTestContainer {
    @Rule public DaggerMockRule daggerRule = DaggerMockUtils.defaultDaggerMockRule();
    @Mock ApiVersionChecker apiVersionChecker;
    public void setupLogic() {
    when(apiVersionChecker.shouldUpdateApplication()).thenReturn(true);
    }
    @Test
    public void testForceAppUpdate_ifAppIsTooOld_shouldShowUpdateAzimoScreen() throws Exception {
    /* Data mocking (App-State unrelated) */
    when(apiVersionChecker.shouldUpdateApplication()).thenReturn(true);
    /* Config init */
    TestLauncher.init()
    .setting_setInputToUser(TestUsers.getTestUserNoTransfersNoRecipients())
    .setting_useStartingPoint(LauncherStartingCondition.USER)
    .run();
    /* Test Start */
    appTooOldNavi().waitForEnteredActivity(AppTooOldActivity.class);
    appTooOldAsserts().assertThat_correctTitleIsDisplayed();
    appTooOldAsserts().assertThat_correctDescriptionIsDisplayed();
    appTooOldAsserts().assertThat_correctButtonDescriptionIsDisplayed();
    }
    }
    Mocking
    Init state
    activity
    test

    View Slide

  64. END-TO-END
    TESTING

    View Slide

  65. Simulate end user 5
    End-to-end features
    Up to 2 hours ⏲

    View Slide

  66. View Slide

  67. How to get there ?

    Be 1% better every release

    View Slide

  68. early 2015 - no unit tests (product launch)
    late 2015 - 20% coverage, manual testing
    2016 - 40% coverage, started QA engineering
    2017 - 60% coverage, core features e2e
    2018 - 50-60% coverage, all features e2e
    someday - “Lint zero” project

    View Slide

  69. Automation Test Supervisor
    Automated test emulator run plugin
    Check on
    github.com/AzimoLabs

    View Slide

  70. Testing
    Fast
    feedback
    QA
    Release
    candidate
    Internal build RC distrib
    auto
    min x
    Rollout
    auto
    asap
    RC
    testing
    x2
    x3
    x1
    Development
    Coding
    Code
    checks

    ✔ ✔

    View Slide

  71. Testing
    Fast
    feedback
    QA
    Release
    candidate
    Internal build RC distrib
    auto
    min x
    Rollout
    auto
    asap
    RC
    testing
    x2
    x3
    x1
    Development
    Coding Code
    checks

    ✔ ✔

    View Slide

  72. Write code that matters 8
    Separate logic from SDK /
    Code must be testable

    View Slide


  73. IF WRITING TESTS IS HARD, THAT
    MIGHT BE A BUG

    View Slide

  74. Activity
    Data Navigation
    Networking
    UI View logic
    ALL-IN-ONE
    Testing?
    External QA, only end-to-end.

    View Slide

  75. Model
    Presenter
    View
    Database
    Navigation API
    View logic
    TESTABLE
    Data logic
    Unit testing - moderate (a lot to mock, incl. SDK)
    Integration testing - moderate
    End-to-end testing - easy
    Stores

    View Slide

  76. View
    Stores
    Navigator
    API
    View logic Use cases
    Repositories,
    Managers
    5-STARS
    Database

    View Slide

  77. UNIT TESTING
    View
    Stores
    Navigator
    API
    View logic Use cases
    Repositories,
    Managers
    Database

    View Slide

  78. View
    Stores
    Navigator
    API
    View logic Use cases
    Repositories,
    Managers
    Database
    UNIT TESTING

    View Slide

  79. View
    Stores
    Navigator
    API
    View logic Use cases Repositories,
    Managers
    Database
    UNIT TESTING

    View Slide

  80. System under test Mocks
    INTEGRATION TESTING
    View
    Stores
    Navigator
    API
    View logic Use cases Repositories,
    Managers
    Database

    View Slide

  81. System under test
    View
    Stores
    Navigator
    API
    View logic Use cases
    Repositories,
    Managers
    Database
    END-TO-END TESTING

    View Slide

  82. EASY TO “THREAD”
    Always UI
    Ensure
    background
    thread
    Always
    background
    View
    Stores
    Navigator
    API
    View logic Use cases
    Repositories,
    Managers
    Database

    View Slide

  83. SDK VS LOGIC
    Android
    SDK
    Plain code
    View
    Stores
    Navigator
    API
    View logic Use cases
    Repositories,
    Managers
    Database

    View Slide

  84. THE BEST
    ARCHITECTURE
    The one that makes the developers
    team the most productive.

    View Slide

  85. ARCHITECTURE PATTERNS
    • MVP, MVVM
    • App Architecture
    Components
    • Others (see Android
    Blueprints)
    github.com/googlesamples/

    android-architecture

    View Slide

  86. WRITE LOGIC
    THAT MATTERS!
    GENERATE EVERYTHING ELSE. 8

    View Slide

  87. @BindView(R.id.tvTitle)
    TextView tvTitle;
    @BindColor(R.color.brand)
    int colorBrand;
    @BindString(R.string.hello)
    String strHello;
    @Override
    protected void onCreate(Bundle state) {
    // LOGIC THAT MATTERS TO YOU
    }
    WITH BUTTERKNIFE

    View Slide

  88. private TextView tvTitle;
    private String title;
    private int colorBrand;
    @Override
    protected void onCreate(Bundle state) {
    // A LITTLE BIT OF INIT
    tvTitle = findViewById(R.string.project_id);
    colorBrand = ContextCompat.getColor(this, R.color.brand);
    strHello = getString(R.string.strHello);
    // A LITTLE BIT OF LOGIC
    }
    WITHOUT BUTTERKNIFE

    View Slide

  89. @Inject
    ActivityPresenter presenter;
    @Inject
    AnimationUtils animationUtils;
    @Inject
    ListAdapter listAdapter;
    @Override
    protected void onCreate(Bundle state) {
    // LOGIC THAT MATTERS TO YOU
    }
    WITH DAGGER

    View Slide

  90. ActivityPresenter presenter;
    AnimationUtils animationUtils;
    ListAdapter listAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // A LITTLE BIT OF INIT
    presenter = new ActivityPresenter(this, /* x other dependencies */);
    animationUtils = new AnimationUtils(this);
    listAdapter = new listAdapter(this, /* y other dependencies*/);
    // A LITTLE BIT OF LOGIC

    // …
    }
    WITHOUT DAGGER

    View Slide

  91. MORE DAGGER
    (INJECT EVERYTHING)
    @ActivityScope
    public class SupportAdapter extends SimplestRecyclerViewAdapter {
    public static final int ITEM_VIEW_TYPE_CATEGORY = 0;
    //…and 11 other types
    //Factories for all items types
    private final Map vhFactories;
    @Inject
    public SupportAdapter(Map vhFactories) {
    this.vhFactories = vhFactories;
    }
    @Override
    public SupportViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    //Take factory for given view type and create ViewHolder
    return vhFactories.get(viewType).createViewHolder(parent);
    }
    }

    View Slide

  92. @Module
    abstract class BindingsModule {
    @Binds @IntoMap
    @IntKey(ITEM_VIEW_TYPE_LOADING)
    abstract SupportViewHolderFactory bindLoadingViewHolderFactory(LoadingViewHolderFactory factory);
    @Binds @IntoMap
    @IntKey(ITEM_VIEW_TYPE_ARTICLE)
    abstract SupportViewHolderFactory bindArticleViewHolderFactory(ArticleViewHolderFactory factory);
    @Binds @IntoMap
    @IntKey(ITEM_VIEW_TYPE_CTA_CHAT)
    abstract SupportViewHolderFactory bindCtaChatViewHolderFactory(CtaChatViewHolderFactory factory);
    // and all other factories…
    }

    View Slide

  93. @AutoFactory(implementing = SupportViewHolderFactory.class)
    public class ArticleViewHolder extends SupportViewHolder {
    @BindView(R2.id.text) TextView text;
    @BindView(R2.id.divider) View divider;
    ArticleViewModel viewModel;
    private final ArticleListener host;
    public ArticleViewHolder(@Provided LayoutInflater inflater,
    @Provided ArticleListener host,
    ViewGroup parent) {
    super(inflater, parent, R.layout.as_item_support_article);
    this.host = host;
    }
    @Override
    public void bind(SupportViewModel item) {
    viewModel = (ArticleViewModel) item;
    text.setText(viewModel.title);
    ViewUtils.visibility(divider, !viewModel.last);
    }
    @OnClick(R2.id.contentContainer)
    void itemClicked() {
    host.articleSelected(viewModel);
    }
    }
    Logic that matters!
    Code generated for us

    View Slide

  94. DAGGER2RECIPES
    github.com/frogermcs
    • Async injection
    • MultiBinding + Autofactory
    • Demo app with all-in-one
    • Multimodule demo app

    View Slide

  95. void handleApiErrorResponse(HttpException e) {
    GeneralErrorResponse errorResponse = errorResponseParser.valueOf(e.response());
    switch (errorResponse.genericErrorCode) {
    case ErrorCodes.CODE_401_DEVICE_NOT_AUTHENTICATED:
    ((BaseActivity) activityCompanion).logoutUser();
    break;
    case ErrorCodes.CODE_403_AUTHENTICATE_ACCOUNT_BLOCKED:
    ((BaseActivity) activityCompanion).showAccountBlockedError();
    break;
    case ErrorCodes.CODE_422_USER_EMAIL_ALREADY_TAKEN:
    if (activityCompanion instanceof CreateAccountActivity) {
    ((CreateAccountActivity) activityCompanion).showEmailAlreadyRegistered();
    }
    break;
    case ErrorCodes.CODE_404_USER_INVALID_INVIATION:
    if (activityCompanion instanceof CreateAccountActivity) {
    ((CreateAccountActivity) activityCompanion).showInvalidInvitation();
    }
    break;
    default:
    handleGeneralError(errorResponse, e);
    break;
    }
    }

    View Slide

  96. ERROR HANDLER
    @AutoHandler
    public interface SimplerErrorListener {
    @ErrorCode(CODE_403_AUTHENTICATE_ACCOUNT_BLOCKED)
    void accountBlocked();
    @ErrorCode(CODE_422_USER_EMAIL_ALREADY_TAKEN)
    void emailTaken();
    @ErrorCode(codes = {"501", "503"})
    void serverError();
    }
    Check on
    github.com/AzimoLabs
    /Api-Error-Handler

    View Slide

  97. MODULARIZE ⚛

    View Slide

  98. Objects graph
    CODE MODULES
    ApiModule
    DataModule UtilsModule
    UserManager
    CountriesRepo
    CurrenciesRepo

    OKHttpClient
    ApiRestClient
    AuthInterceptor
    LoggingInterceptor
    CurrencyFormatter
    ExchangeCalculator

    more…

    View Slide

  99. Objects graph (AppComponent)
    ApiModule
    DataModule UtilsModule more…
    @Singleton
    @Component(
    modules = {
    AppModule.class,
    DataModule.class,
    ApiModule.class,
    UtilsModule.class,
    }
    )
    public interface AppComponent {
    }

    View Slide

  100. Objects graph (AppComponent)
    ApiModule
    DataModule UtilsModule more…
    @Module
    public class ApiModule {
    @Provides
    @Singleton
    public OkHttpClient provideOkHttpClient() {/**/}
    @Provides
    @Singleton
    public Retrofit provideRestAdapter(OkHttpClient okHttpClient) {/**/}
    @Provides
    @Singleton
    public GithubApiService provideGithubApiService(Retrofit restAdapter) {
    return restAdapter.create(GithubApiService.class);
    }
    }

    View Slide

  101. Objects graph (AppComponent)
    ApiModule
    DataModule UtilsModule more…
    Our DataModule
    >600 lines of code

    View Slide

  102. Objects graph (AppComponent)
    FEATURE MODULES
    CountriesModule
    PaymentModule CardsModule
    PaymentManager
    PaymentConfigRepo
    PaymentApiService
    CountriesManager
    CountriesRepository
    CountriesConfig
    CountriesApi
    CardsManager
    CardsRepository
    CardsEncryption
    CardsTokeniseApi
    more…

    View Slide

  103. CountriesModule
    PaymentModule CardsModule
    DataModule
    vs
    >
    ?
    @
    ?
    > @

    View Slide

  104. @Singleton
    @Component(
    modules = {
    AzimoAppModule.class,
    DataModule.class,
    ApiModule.class,
    AppConfigModule.class,
    UtilsModule.class,
    BackendModule.class,
    GcmModule.class,
    ApiAuthorisationModule.class,
    ViewMappersModule.class,
    AnalyticsModule.class,
    MatchingModule.class,
    CardsModule.class,
    FormattersModule.class,
    TransactionDownloadModule.class,
    PeopleDownloadModule.class,
    AppsTrackingModule.class,
    DocUploaderModule.class,
    PricingModule.class,
    ZendeskModule.class,
    PayinModule.class,
    CheckoutModule.class,
    SharedEverywhereModule.class,
    PaymentModule.class,
    RateModule.class,
    EstimatedDeliveryModule.class,
    ZendeskModule.class,
    UserFactsModule.class,
    UserFactsDownloadModule.class,
    FirebaseCardsApiModule.class
    }
    )
    public interface AppComponent {
    }

    View Slide

  105. View Slide

  106. MODULARIZE MORE

    ⚛ ⚛

    View Slide

  107. MULTI-FEATURE APP
    SupportFeature
    App
    ChatModule
    FAQModule
    Support logic
    PaymentFeature
    CardsModule
    Payments logic
    PaymentModule
    Base
    Shared features

    CountriesFeature CurrenciesFeature UserFactsFeature …

    View Slide

  108. SupportFeature
    App
    ChatModule
    FAQModule
    Support logic
    PaymentFeature
    CardsModule
    Payments logic
    PaymentModule
    Base
    Shared features

    CountriesFeature CurrenciesFeature UserFactsFeature …

    • Quick compilation
    • Quick unit tests
    • Clean structure
    MULTI-FEATURE APP

    View Slide

  109. SupportFeature
    App
    ChatModule
    FAQModule
    Support logic
    PaymentFeature
    CardsModule
    Payments logic
    PaymentModule
    Base
    Shared features

    CountriesFeature CurrenciesFeature UserFactsFeature …

    • Configuration 

    complexity
    • Low solutions 

    maturity
    • High entry level
    MULTI-FEATURE APP

    View Slide

  110. THE REALITY
    SupportFeature
    App
    Base
    Shared features
    Still App…
    Countries
    Feature
    UserFacts
    Feature

    PaymentFeature

    Transactions

    Feature
    Still App…

    View Slide

  111. PROJECT MODULARIZATION
    Multi
    module
    setup

    View Slide

  112. PROJECT MODULARIZATION
    Multi
    module
    setup
    Dagger 2
    across
    modules

    View Slide

  113. PROJECT MODULARIZATION
    Multi
    module
    setup
    Dagger 2
    across
    modules
    Unit testing

    View Slide

  114. PROJECT MODULARIZATION
    Multi
    module
    setup
    Dagger 2
    across
    modules
    Unit testing
    Tests 

    coverage

    View Slide

  115. PROJECT MODULARIZATION
    Multi
    module
    setup
    Dagger 2
    across
    modules
    Unit testing
    Tests 

    coverage
    github.com/frogermcs/MultiModuleGithubClient
    End-to-end
    testing
    Proguard
    configuration
    more in the
    future…
    Done during:

    View Slide

  116. ?A
    PEOPLE

    View Slide

  117. Crash-free users >99.5%
    Release once a week

    View Slide

  118. !!!
    Crash-free users >99.5%
    Release once a week

    View Slide


  119. STOP THE LINE

    View Slide

  120. Every employee on the assembly line
    has a responsibility to push a big red button
    that stops everything whenever they notice
    a defect

    View Slide

  121. Release every week + stop the line
    40 releases in last 12 months.

    View Slide

  122. Release weeks in last 12 months

    View Slide

  123. Release once a week - achieved in 83%
    Crash free >99.5% - achieved in 100%

    View Slide

  124. THEY TELL
    We don’t have time do it right
    YOU ASK
    Do you have time to do it twice?

    View Slide

  125. @froger_mcs
    [email protected]
    medium.com/@froger_mcs
    Link to this presentation
    will be there
    More about how we
    build the technology
    Ask me anything
    you want :)

    View Slide