$30 off During Our Annual Pro Sale. View Details »

Remote, lonely and productive

Remote, lonely and productive

Talk given at Android Makers Paris 2017

Being the only developer in a project can be frustrating, everything falls into your shoulders and it's very easy to get trapped into bad habits. You are not alone! There are many tools, practices and services that will make your life easier... and efficient!

Several months ago David joined Help Scout as the only Android Developer with the main purpose of building their Android application from scratch. Coming from an agency environment where there were more than 30 developers willing to discuss and present different ideas, it's been quite a challenge.

This talk shares David's experience, explaining what tools, practices and methodologies he's followed. Being the only developer is no impediment to build great quality code, follow good design patterns and delight your users.

How to do Pull Requests, Code Reviews, Design Reviews, Continuous Delivery and Integration... Whether you are a one man band or work with a team, these tips will help you become a better programmer!

David González

April 11, 2017
Tweet

More Decks by David González

Other Decks in Programming

Transcript

  1. Memories of a remote and lonely Android developer

    View Slide

  2. View Slide

  3. View Slide

  4. When considering a new job,
    53.3%
    said remote options were a top
    priority

    View Slide

  5. A majority of developers
    63.9%
    reported working remotely at least
    one day a month

    View Slide

  6. 11.1%
    say they’re full-time remote or
    almost all the time

    View Slide

  7. http://stackoverflow.com/insights/survey/2017

    View Slide

  8. http://blog.ovidiubokar.com/sites/default/files/images/dilbert-working-from-home.jpg

    View Slide

  9. What does remote mean?

    View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. View Slide

  14. View Slide

  15. View Slide

  16. View Slide

  17. A bad writer could turn a
    2-minute chat into a 10-minute
    chat

    - Nick Francis
    CEO Help Scout

    View Slide

  18. View Slide

  19. Hours
    0
    7.5
    15
    22.5
    30
    Android Studio Slack Github.com Inbox twitter.com facebook.com
    1
    2.5
    3
    4
    8
    22
    1
    3
    2
    3
    6
    25
    March February

    View Slide

  20. Lonely

    View Slide

  21. View Slide

  22. Your main goal is to ship a product

    View Slide

  23. Your main goal is to ship a product

    View Slide

  24. Productive

    View Slide

  25. View Slide

  26. 7 stages of naming

    View Slide

  27. helpscout
    api
    app
    database

    View Slide

  28. app
    common
    data
    domain
    injection

    View Slide

  29. domain
    conversations
    folders
    mailboxes
    session
    search
    settings

    View Slide

  30. mailboxes
    model
    usecases
    view
    MailboxesMVP
    MailboxesPresenter

    View Slide

  31. Static analysis

    View Slide

  32. Static analysis
    apply plugin: 'com.android.application'
    // wrapper task to run all static code analysis
    task staticAnalysis {
    description 'Runs checkstyle, findbugs and pmd against the
    main sources.'
    group 'Verification'
    }
    def sourceSetMain = android.sourceSets.main.java.srcDirs
    def rulesDir = new File(project.teamPropsDir, 'rules')
    def ignoreFail = !project.failFastOnError
    def includeSrcPatternApplication = 'net/helpscout/**/*.java'
    def excludeSrcPatternClass = 'net/helpscout/android/**/R.java'
    def excludeSrcPatternGeneratedFiles = '**/gen/**'
    def excludeSrcDagger = 'net/helpscout/android/injection*.java'

    View Slide

  33. Static analysis
    apply plugin: 'com.android.application'
    // wrapper task to run all static code analysis
    task staticAnalysis {
    description 'Runs checkstyle, findbugs and pmd against the
    main sources.'
    group 'Verification'
    }
    def sourceSetMain = android.sourceSets.main.java.srcDirs
    def rulesDir = new File(project.teamPropsDir, 'rules')
    def ignoreFail = !project.failFastOnError
    def includeSrcPatternApplication = 'net/helpscout/**/*.java'
    def excludeSrcPatternClass = 'net/helpscout/android/**/R.java'
    def excludeSrcPatternGeneratedFiles = '**/gen/**'
    def excludeSrcDagger = 'net/helpscout/android/injection*.java'

    View Slide

  34. Static analysis
    apply plugin: 'com.android.application'
    // wrapper task to run all static code analysis
    task staticAnalysis {
    description 'Runs checkstyle, findbugs and pmd against the
    main sources.'
    group 'Verification'
    }
    def sourceSetMain = android.sourceSets.main.java.srcDirs
    def rulesDir = new File(project.teamPropsDir, 'rules')
    def ignoreFail = !project.failFastOnError
    def includeSrcPatternApplication = 'net/helpscout/**/*.java'
    def excludeSrcPatternClass = 'net/helpscout/android/**/R.java'
    def excludeSrcPatternGeneratedFiles = '**/gen/**'
    def excludeSrcDagger = 'net/helpscout/android/injection*.java'

    View Slide

  35. Static analysis
    apply plugin: 'checkstyle'
    task checkstyleMain(type: Checkstyle) {
    ...
    }
    staticAnalysis.dependsOn checkstyleMain
    task verifyNoCheckstyleWarnings {
    doLast {
    File warningsFile = file(‘buildMessage/reports/
    checkstyle/main.xml')
    if (warningsFile.exists() &&
    warningsFile.text.contains("throw new GradleException("There were checkstyle
    warnings! For more info check $warningsFile")
    }
    }
    }

    View Slide

  36. Static analysis
    apply plugin: 'checkstyle'
    task checkstyleMain(type: Checkstyle) {
    ...
    }
    staticAnalysis.dependsOn checkstyleMain
    task verifyNoCheckstyleWarnings {
    doLast {
    File warningsFile = file(‘buildMessage/reports/
    checkstyle/main.xml')
    if (warningsFile.exists() &&
    warningsFile.text.contains("throw new GradleException("There were checkstyle
    warnings! For more info check $warningsFile")
    }
    }
    }

    View Slide

  37. Static analysis
    apply plugin: 'checkstyle'
    task checkstyleMain(type: Checkstyle) {
    ...
    }
    staticAnalysis.dependsOn checkstyleMain
    task verifyNoCheckstyleWarnings {
    doLast {
    File warningsFile = file(‘buildMessage/reports/
    checkstyle/main.xml')
    if (warningsFile.exists() &&
    warningsFile.text.contains("throw new GradleException("There were checkstyle
    warnings! For more info check $warningsFile")
    }
    }
    }

    View Slide

  38. The process

    View Slide

  39. View Slide

  40. View Slide

  41. Who is going to review my code?

    View Slide

  42. Who is going to review my code?

    View Slide

  43. Page heading
    Open Pull Request !rst, the old way

    View Slide

  44. Pull Requests now

    View Slide

  45. Pull Requests now

    View Slide

  46. The planning

    View Slide

  47. View Slide

  48. View Slide

  49. View Slide

  50. View Slide

  51. View Slide

  52. Github integration in Android Studio

    View Slide

  53. View Slide

  54. View Slide

  55. Release process

    View Slide

  56. The pipeline

    View Slide

  57. Amazon Device Farm and Jenkins

    View Slide

  58. Travis
    before_install:
    - chmod +x team-props/firebase/firebase_tests.sh
    - chmod +x team-props/firebase/proguard_mapping.sh
    - chmod +x team-props/release/release.sh
    script:
    - ./gradlew clean build
    - team-props/firebase/firebase_tests.sh
    - team-props/release/release.sh
    - team-props/firebase/proguard_mapping.sh

    View Slide

  59. Firebase Test Lab
    #!/bin/bash
    # Use gcloud from Google to run Espresso tests
    # on Firebase Test Lab
    BRANCH='master';
    ACCOUNT='your.iam.gserviceaccount.com';
    FILE='./team-props/firebase/google_cloud_secret.json';
    if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
    echo "Skipping tests on Firebase: was pull request."
    elif [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then
    echo "Skipping tests on Firebase: wrong branch. Expected
    '$BRANCH' but was '$TRAVIS_BRANCH'."
    else
    echo "Starting tests on Firebase"
    https://firebase.google.com/docs/test-lab/command-line

    View Slide

  60. Firebase Test Lab
    https://firebase.google.com/docs/test-lab/command-line
    ./google-cloud-sdk/bin/gcloud auth activate-service-
    account $ACCOUNT --key-file $FILE
    ./google-cloud-sdk/bin/gcloud beta test android run --
    async --type instrumentation --app ./app/build/outputs/
    apk/app-debug.apk --test ./app/build/outputs/apk/app-
    debug-androidTest.apk --device-ids Nexus5 --os-version-ids
    22 --project help-scout

    View Slide

  61. Publish to Alpha / Beta
    apply plugin: 'com.github.triplet.play'
    def playstoreServiceAccount = System.env.PLAYSTORE_SERVICE_ACCOUNT
    def playstorePk12File = System.env.PLAYSTORE_PK12_FILE
    if (playstoreServiceAccount && playstorePk12File) {
    play {
    track = 'beta'
    serviceAccountEmail = playstoreServiceAccount
    pk12File = rootProject.file(playstorePk12File)
    }
    }
    project.afterEvaluate {
    def publishApkRelease = project.tasks.getByName("publishApkRelease")
    def incrementVersionForRelease =
    project.tasks.getByName("incrementVersionForRelease")
    publishApkRelease.dependsOn incrementVersionForRelease

    View Slide

  62. Publish to Alpha / Beta
    #!/bin/bash
    # Use Google Play plugin to release an app version to the
    Alpha Channel
    BRANCH='master';
    GRADLE_TASK='publishProductionRelease';
    if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
    echo "Skipping release on Google Play: was pull
    request."
    elif [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then
    echo "Skipping release on Google Play: wrong branch.
    Expected '$BRANCH' but was '$TRAVIS_BRANCH'."
    else
    echo "Starting release on Google Play"
    ./gradlew $GRADLE_TASK
    echo "APK upload to Google Play complete"
    fi

    View Slide

  63. Publish to Alpha / Beta

    View Slide

  64. Publish to Alpha / Beta

    View Slide

  65. Thank you!
    David González
    @dggonzalez
    [email protected]

    View Slide

  66. Remote: Office not required (book)
    - https://goo.gl/yAZOAu
    1
    Resources
    What We’ve Learned Building a Remote Culture
    - https://www.helpscout.net/blog/remote-culture/
    Why I Stopped Using Multiple Monitors
    - https://hackernoon.com/why-i-stopped-using-multiple-monitors-bfd87efa2e5b
    The source of all technical debt
    - https://speakerdeck.com/malmstein/the-source-of-all-technical-debt
    Good naming is a process, not a single step
    - http://arlobelshee.com/good-naming-is-a-process-not-a-single-step/
    Stack Overflow Developer Survey 2017
    - http://stackoverflow.com/insights/survey/2017

    View Slide