DevOps in

Flutter Development 

Majid Hajian

mhadaily

 Beautiful

Fast

Open

mhadaily

Productive

 Beautiful

Fast

Open

mhadaily

Zapp.run

Continuous Planning and Integration

Continuous Testing and Monitoring

Continuous Delivery and Deployment

mhadaily

What we will learn today

Branching Flow

1

CD/CI

1

Deployment Flow

1

job Automation

1

mhadaily

ME.dart

import 'package:flutter/material.dart';
MaterialApp(
   ThemeData(
        name: "Majid Hajian",
        location: "Oslo, Norway",
        description: '''
                Google Developer Expert
        	Passionate Software engineer, 
	        Community Leader, Author and international Speaker
         ''',
        main: "Head of DevRel at Invertase.io",
        homepage: "https://www.majidhajian.com",
        socials: {
          twitter: "https://www.twitter.com/mhadaily",
          github: "https://www.github.com/mhadaily"
        },
        author: {
          Pluralsight: "www.pluralsight.com/authors/majid-hajian",
          Apress: "Progressive Web App with Angular, Book",
          PacktPub: "PWA development",
          Udemy: "PWA development",
        }
        founder: "Softiware As (www.Softiware.com)"
        devDependencies: {
          tea: "Ginger", 
          mac: "10.14+",
        },
        community: {
          MobileEraConference: "Orginizer",
          FlutterVikings: "Orginizer", 
          FlutterDartOslo: "Orginizer",
          GDGOslo: "Co-Orginizer",
          DevFestNorway: "Orginizer",
          ...more
        }));

mhadaily

Find me on the internet by

Head of DevRel at Invertase

From Code to User

The Flow

mhadaily

Company Testers 

Internal Testers 

External Testers / Beta 

Production

mhadaily

Git Flow

Scrum

mhadaily

mhadaily

Company Testers 

Internal Testers 

External Testers / Beta 

Production

mhadaily

if [[ "$FH_BRANCH" == "master" ]]; then
  export DEPLOYMENT_LANE="production" 
  
elif [[ "$FH_BRANCH" == "develop" ]]; then
  export DEPLOYMENT_LANE="internal"
  
elif [[ "$FH_BRANCH" =~ ^release\/.*$ ]]; then
  export DEPLOYMENT_LANE="beta"
  
else
  export DEPLOYMENT_LANE="feature"
fi

Production

Beta

Production

Alpha

Internal

Production

App Store

TestFlight

mhadaily

Production

Beta

Production

Alpha

Internal

App Store

TestFlight

End Users

Internal Testers

Beta Testers

mhadaily

mhadaily

mhadaily

Code Quality

mhadaily

void main(List<String> arguments) {
  analyze.main();
  format.main();
  test.main();
}

mhadaily

void main(List<String> arguments) {
  analyze.main();
  format.main();
  test.main();
}
void main() {
  stdout.write('Start Analyzing...\n');
  final ProcessResult result = Process.runSync(
    'flutter',
    <String>['analyze', '--no-pub'],
    runInShell: true,
  );
  checkResult(result);
}

mhadaily

Git Hooks

mhadaily

mhadaily

#!/bin/bash
# Created For Majid's App 
if [ ! -f /Users/majid/Projects/app/scripts/git_hooks/pre_commit.dart ]; then
    # Dart script not found, skipping hook"
    exit 0
fi
echo "> Running pre_commit hook..."
DART_EXIT_CODE=0
dart /Users/majid/Projects/app/scripts/git_hooks/pre_commit.dart "$@"
DART_EXIT_CODE=$?
if [[ ${DART_EXIT_CODE} -ne 0 ]]; then
  echo ""
  echo "> Error detected in pre_commit hook."
        exit 1
fi
> cat .git/hooks/pre-commit

mhadaily

void main(List<String> arguments) {
  analyze.main();
  format.main();
  test.main();
}

mhadaily

CI / CD

mhadaily

mhadaily

Why Should we start doing this?

 

1- Improve speed to market and our internal testers

2-  Ensure quality

3- Deploying is seamless

4- improves disaster recovery 

5- improve business agility

mhadaily

mhadaily

  1. Capture screenshots automatically
  2. Distribute beta builds
  3. publish your app with the push of a button
  4. automate code signing for iOS
  5. Handle your build by code (code as infrastructure)  
  6. Open source / Written in Ruby / Gem
  7. Handle push notificaiton certificates out of the box

mhadaily

fastlane is an open source platform aimed at simplifying Android and iOS deployment.
fastlane lets you automate every aspect of your development and release workflow.

mhadaily

mhadaily

.
├── Appfile
├── Deliverfile
├── Fastfile
├── Matchfile
├── Pluginfile
├── android
│   ├── icons
│   ├── metadata
│   └── screenshots
├── devices.txt
├── ios
│   ├── metadata
│   ├── push_certificates
│   └── screenshots
└── report.xml
.
├── CHANGELOG.md
├── Gemfile
├── Gemfile.lock
├── README.md
├── analysis_options.yaml
├── android
├── assets
├── build
├── build.yaml
├── dartdoc_options.yaml
├── docs
├── fastlane
├── gen
├── github_action.sh
├── ios
├── lib
├── local_packages
├── pubspec.lock
├── pubspec.yaml
├── res
├── scripts
├── setup.dart
├── test
├── travis_before_script.sh
├── travis_script.sh
└── web

mhadaily

require 'aws-sdk'
app_name = "ENV["APP_NAME"]" #{app_name}
username = "ENV["USERNAME"]" #{username} // Apple ID
slack_url = ENV["SLACK_DEPLOY_WEBHOOK"]
raw_version = Psych.load_file('../pubspec.yaml')['version']
# Only major, minor, patch digits and dots.
version_number = /\d+\.\d+\.\d+/.match(raw_version)[0]
build_number = 1
codesigning_identity = "ENV["CODESIGNING_IDENTITY"]"
app_identifier = CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier)
package_name = CredentialsManager::AppfileConfig.try_fetch_value(:package_name)
export_team_id = CredentialsManager::AppfileConfig.try_fetch_value(:team_id)
changelog = ''
emojified_changelog = ''
# Prevent Fastlane from overwriting README.md
skip_docs
# xcversion(version: "11.2.1")
# Get an Amazon S3 resource
if (ENV["AWS_ACCESS_KEY_ID"])
  s3 = Aws::S3::Resource.new(region: 'eu-central-1')
# Get the bucket by name
  push_certificate_bucket = s3.bucket('papp-app-push-notification-certificates')
end
default_platform(:ios)

Fastfile

platform :android do

# your lanes for Android here 

end


platform :ios do

# your lanes for iOS here 

end

mhadaily

Fastfile

platform :android do

  desc "Build and release to beta"
  lane :beta_internal_release do
    prepare(release: false)
    setup_new_changelog
    release_to_store(track: 'internal')
  end
  
end

mhadaily

platform :ios do

  desc "Push a new release to TestFlight"
  lane :build_and_deploy_testflight do |options|
    puts "Build and Deploy TestFlight started..."
    prepare(environment: options[:environment])
    setup_new_changelog
    upload_to_testflight(
        skip_waiting_for_build_processing: true,
        ipa: "Runner.ipa",
        username: username,
    #changelog: changelog, # it's possible to 
    #add AppInfo, BuildInfo and more, also localize it.
    )
    slack(
        message: "#{version_number}+#{build_number}: iOS beta released to 
        TestFlight #{options[:channel]} \n Changelog: \n #{emojified_changelog}",
        slack_url: slack_url,
    )
    # git_actions(release: false)
  end
end

mhadaily

platform :ios do

  desc "Prepare and archive app"
  lane :prepare do |options|
    puts "Building and deploying version #{version_number}, current build #{latest_testflight_build_number}"
    if (ENV["AWS_ACCESS_KEY_ID"])
      push_notification_certificates
    end
    handle_versioning
    flutter_build_no_sign
    generate_icons
    beta_badge(hide: options[:release])
    code_signing
    build_ios_app(
        workspace: workspace_path,
        configuration: "Release",
        scheme: "Runner",
        export_team_id: export_team_id,
        export_method: "app-store",
        # Verify that the right signing identity is used for publishing.
        codesigning_identity: codesigning_identity,
        xcargs: "DEVELOPMENT_TEAM='#{export_team_id}'"
    )
  end
end

mhadaily

Share one code signing identity across your development team to simplify your codesigning setup and prevent code signing issues.

 

codesigning.guide

mhadaily

 

match

🔄 Automatically sync your iOS keys and profiles across all your team members using git
📦 Handle all the heavy lifting of creating and storing your certificates and profiles
💻 Setup codesigning on a new machine in under a minute
🎯 Designed to work with apps with multiple targets and bundle identifiers
🔒 You have full control over your files and Git repo, no third party service involved
Provisioning profile will always match the correct certificate
💥 Easily reset your existing profiles and certificates if your current account has expired or invalid profiles
♻️ Automatically renew your provisioning profiles to include all your devices using the --force option
👥 Support for multiple Apple accounts and multiple teams
Tightly integrated with fastlane to work seamlessly with gym and other build tools

mhadaily

#Matchfile

git_url(ENV['REPO'])
storage_mode("git")
type("appstore") # The default type, can be: appstore, adhoc, enterprise or development
app_identifier(["com.your.app"])
username(ENV['USERNAME']) # Your Apple Developer Portal username

Google Cloud Storage

or

Github

mhadaily

platform :ios do
  desc "Sing code using Match and fastlane"
  lane :code_signing do
    # Stop fastlane from echoing back ENV var.
    # Doesn't matter too much since Travis doesn't echo back encrypted variables
    # anyway :D
    suppress_output {
      # Retrieves all the necessary certs and provisioning profiles.
      # match   # alias for "sync_code_signing"
      sync_code_signing(readonly: true)
    }
    puts 'Certificates and profiles installed'
    # Modify the Xcode project to use the new team and profile.
    # It will put the git state to dirty but Travis will be wiped after
    # then run session.
    disable_automatic_code_signing
    update_project_provisioning(
        build_configuration: 'Release',
        profile: ENV["sigh_#{app_identifier}_appstore_profile-path"]
    )
  end
end

mhadaily

platform :ios do
  desc "Handle version and build number"
  lane :handle_versioning do
    build_number = latest_testflight_build_number + 1
    puts "new build number now is 
    #{build_number} and build 
    version is #{version_number}"
  end
end

platform :android do
  desc "Handle version and build number"
  lane :handle_versioning do
    internal_number = google_play_track_version_codes(track: 'internal')[0].to_i
    beta_number = google_play_track_version_codes(track: 'beta')[0].to_i
    alpha_number = google_play_track_version_codes(track: 'alpha')[0].to_i
    production_number = google_play_track_version_codes(track: 'production')[0].to_i
    # make sure we know all release number and bump latest version
    build_number = [internal_number, beta_number, production_number, alpha_number].max + 1
    puts "new build number now is #{build_number} and build version is #{raw_version}"
  end
end

Build  Number / Version

mhadaily

platform :ios do
  desc "Build app with no signing for ios and increase build number or version number"
  lane :flutter_build_no_sign do
    Dir.chdir "../#{project_ios_path}" do
      sh("flutter", "doctor")
      sh("flutter", "packages", "get")
      sh("flutter", "clean")
      sh("flutter", "test")
    end
  end
end

Run Test / other commands

mhadaily

platform :android do
  desc "Create change log and make it ready for both slack and google play"
  lane :setup_new_changelog do
    changelog = read_changelog(
        changelog_path: "../CHANGELOG.md",
        section_identifier: "[Build: #{version_number}]", # Specify what section to read
        excluded_markdown_elements: ['-', '###'] # Specify which markdown elements should be excluded
    )
    emojified_changelog = emojify_changelog

    # write for Google store at the moment we have only no-NO
    path = "./metadata/android/no-NO/changelogs"
    dir = File.dirname(path)
    unless File.directory?(dir)
      FileUtils.mkdir_p(dir)
    end
    path << "/#{build_number}.txt"
    File.new(path, 'w')
    File.write(path, changelog)

    puts "Changelog for version #{version_number}+#{build_number}: #{changelog}"
  end
end

Change Log / Metadata

mhadaily

platform :android do
  
  desc "update git"
  lane :git_actions do
    # Ensure that your git status is not dirty
    ensure_git_status_clean
    #add git tag
    add_git_tag(build_number: version_number)
    # Commit the version bump
    commit_version_bump
    # Push the new commit and tag back to your git remote
    push_to_git_remote
  end
  
  # and many more 
  # capture screenshots for android
  # upload to Google Play
  # Run tests
  # generate icons
  # add beta badge 
  # lots of other plugins 
end

more Lanes

mhadaily

  desc "Build and release to internal tester"
  lane :internal do
    prepare(release: false, environment: 'development')
    setup_new_changelog
    release_to_store(track: 'internal')
  end

  desc "Deploy a new version to the Google Play production / First to beta and then we can promote to Production Manually"
  lane :prod do
    prepare(release: true, environment: 'production')
    setup_new_changelog
    release_to_store(track: "beta")
  end

  desc "Deploy a new version to the Google Play Beta"
  lane :beta do
    prepare(release: true, environment: 'production')
    setup_new_changelog
    release_to_store(track: "alpha")
  end

Deployment Lanes

mhadaily

if [[ "$1" == "ios" ]] || [[ "$1" == "android" ]]; then
  bundle exec fastlane "$1" "$DEPLOYMENT_LANE"
fi

mhadaily

Summary

mhadaily

Git Flow

1

Fastlane / CD - CI

1

Branching + Release flow

1

What we have learned

mhadaily

Majid Hajian

@mhadaily

Slides and link to source code

slides.com/mhadaily

SVG icons credited to undraw.co

Made with Slides.com