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

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. A bad writer could turn a 2-minute chat into a

    10-minute chat “ - Nick Francis CEO Help Scout
  2. 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
  3. 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'
  4. 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'
  5. 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'
  6. 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("<error ")) { throw new GradleException("There were checkstyle warnings! For more info check $warningsFile") } } }
  7. 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("<error ")) { throw new GradleException("There were checkstyle warnings! For more info check $warningsFile") } } }
  8. 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("<error ")) { throw new GradleException("There were checkstyle warnings! For more info check $warningsFile") } } }
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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