Majid Hajian
Majid Hajian is a passionate software developer with years of developing and architecting complex web and mobile applications. He is passionate about web platform especially flutter, IoT, PWAs, and performance.
Majid Hajian
mhadaily
mhadaily
mhadaily
Zapp.run
mhadaily
Branching Flow
CD/CI
Deployment Flow
job Automation
mhadaily
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
}));
Find me on the internet by
Head of DevRel at Invertase
The Flow
mhadaily
Company Testers
Internal Testers
External Testers / Beta
Production
mhadaily
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
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
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
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.
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
or
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
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
mhadaily
Git Flow
Fastlane / CD - CI
Branching + Release flow
What we have learned
mhadaily
Majid Hajian
@mhadaily
Slides and link to source code
slides.com/mhadaily
SVG icons credited to undraw.co
By Majid Hajian
Majid Hajian is a passionate software developer with years of developing and architecting complex web and mobile applications. He is passionate about web platform especially flutter, IoT, PWAs, and performance.