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!

820df515de752bffa0ce2644a7927186?s=128

David González

April 11, 2017
Tweet

Transcript

  1. Memories of a remote and lonely Android developer

  2. None
  3. None
  4. When considering a new job, 53.3% said remote options were

    a top priority
  5. A majority of developers 63.9% reported working remotely at least

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

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

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

  9. What does remote mean?

  10. None
  11. None
  12. None
  13. None
  14. None
  15. None
  16. None
  17. A bad writer could turn a 2-minute chat into a

    10-minute chat “ - Nick Francis CEO Help Scout
  18. None
  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
  20. Lonely

  21. None
  22. Your main goal is to ship a product

  23. Your main goal is to ship a product

  24. Productive

  25. None
  26. 7 stages of naming

  27. helpscout api app database

  28. app common data domain injection

  29. domain conversations folders mailboxes session search settings

  30. mailboxes model usecases view MailboxesMVP MailboxesPresenter

  31. Static analysis

  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'
  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'
  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'
  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("<error ")) { throw new GradleException("There were checkstyle warnings! For more info check $warningsFile") } } }
  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("<error ")) { throw new GradleException("There were checkstyle warnings! For more info check $warningsFile") } } }
  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("<error ")) { throw new GradleException("There were checkstyle warnings! For more info check $warningsFile") } } }
  38. The process

  39. None
  40. None
  41. Who is going to review my code?

  42. Who is going to review my code?

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

  44. Pull Requests now

  45. Pull Requests now

  46. The planning

  47. None
  48. None
  49. None
  50. None
  51. None
  52. Github integration in Android Studio

  53. None
  54. None
  55. Release process

  56. The pipeline

  57. Amazon Device Farm and Jenkins

  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
  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
  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
  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
  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
  63. Publish to Alpha / Beta

  64. Publish to Alpha / Beta

  65. Thank you! David González @dggonzalez malmstein@gmail.com

  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