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. Anyone who has never made a mistake
    has never tried anything new.
    -Albert Einstein

    View full-size slide

  2. IT BIZ

    Me

    View full-size slide

  3. Fail fast,
    Fail often,
    Fail better,
    Succeed

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  6. New app once a week???

    View full-size slide

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

    View full-size slide

  8. 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 full-size slide

  9. 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 full-size slide

  10. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  15. 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 full-size slide

  16. 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 full-size slide

  17. 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 full-size slide

  18. 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 full-size slide

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

    View full-size slide

  20. 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 full-size slide

  21. 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 full-size slide

  22. 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 full-size slide

  23. Coding
    Testing
    and
    review
    x2
    x3
    x1
    Release
    candidate

    View full-size slide


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

    View full-size slide

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

    View full-size slide

  26. Sign

    apk
    Release

    notes
    Internal

    distrib
    Update
    assets
    Store

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

    View full-size slide

  27. +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 full-size slide

  28. +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 full-size slide

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

    View full-size slide

  30. 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 full-size slide

  31. 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 full-size slide

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

    View full-size slide

  33. Every
    24hrs
    Update 

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

    View full-size slide

  34. 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 full-size slide

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

    View full-size slide

  36. *
    QUALITY
    ASSURANCE

    View full-size slide


  37. Key feature can be tested manually
    only once.

    View full-size slide

  38. 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 full-size slide

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

    View full-size slide

  40. @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 full-size slide

  41. @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 full-size slide


  42. COVERAGE 100% (?)

    View full-size slide

  43. @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 full-size slide

  44. 2 unit tests,
    0 integration tests

    View full-size slide

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

    View full-size slide

  46. INTEGRATION
    TESTING

    View full-size slide

  47. Built by QA engineers
    Run on emulator
    Hermetic env

    View full-size slide

  48. Data and settings
    Use cases,
    presentation logic

    View full-size slide

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

    View full-size slide

  50. mocks System under test
    Use cases,
    presentation logic

    View full-size slide

  51. 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 full-size slide

  52. Android instrumentation test template
    AVD
    manager
    System under
    testing

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  55. @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 full-size slide

  56. @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 full-size slide

  57. END-TO-END
    TESTING

    View full-size slide

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

    View full-size slide

  59. How to get there ?

    Be 1% better every release

    View full-size slide

  60. 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 full-size slide

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

    View full-size slide

  62. 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 full-size slide

  63. 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 full-size slide

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

    View full-size slide


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

    View full-size slide

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

    View full-size slide

  67. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    android-architecture

    View full-size slide

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

    View full-size slide

  79. @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 full-size slide

  80. 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 full-size slide

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

    View full-size slide

  82. 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 full-size slide

  83. 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 full-size slide

  84. @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 full-size slide

  85. @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 full-size slide

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

    View full-size slide

  87. 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 full-size slide

  88. 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 full-size slide

  89. MODULARIZE ⚛

    View full-size slide

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

    OKHttpClient
    ApiRestClient
    AuthInterceptor
    LoggingInterceptor
    CurrencyFormatter
    ExchangeCalculator

    more…

    View full-size slide

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

    View full-size slide

  92. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  96. @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 full-size slide

  97. MODULARIZE MORE

    ⚛ ⚛

    View full-size slide

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

    CountriesFeature CurrenciesFeature UserFactsFeature …

    View full-size slide

  99. 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 full-size slide

  100. 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 full-size slide

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

    PaymentFeature

    Transactions

    Feature
    Still App…

    View full-size slide

  102. PROJECT MODULARIZATION
    Multi
    module
    setup

    View full-size slide

  103. PROJECT MODULARIZATION
    Multi
    module
    setup
    Dagger 2
    across
    modules

    View full-size slide

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

    View full-size slide

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

    coverage

    View full-size slide

  106. 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 full-size slide

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

    View full-size slide

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

    View full-size slide


  109. STOP THE LINE

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  112. Release weeks in last 12 months

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  115. @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 full-size slide