SlideShare a Scribd company logo
1 of 92
Songdo
GitHub Actions를 활용한
Flutter 배포 자동화하기
SuJang Yang
Tech Lead, @bluefrog
GDG Songdo Organizer (2020~ )
Tech Lead @bluefrog (2020 ~ )
- Android
- Flutter
- Back-end + DevOps
I’m an App Developer.
yangsterchief@duck.com
https://github.com/yangster-chief
이런 분들에게 추천합니다 👍
1. Android, iOS 배포 자동화에 관심있으신 분들
2. Flavor 를 활용한 개발환경, 배포환경을 구분하여 배포하는 방법에
관심있으신 분들
3. 초기 구축 비용 때문에Jenkins 같은 자동화 도구 도입을 고민하는 분들
Automation Scenario
Build and Deploy Flow
Build and Deploy Flow
App Signing in Android
App Signing in iOS
Build and Deploy Flow
Build and Deploy Flow
Summary
Build variants in Flutter
https://docs.flutter.dev/deployment/flavors
● 하나의 프로젝트 환경에서 빌드 결과물을 물리적으로 분리
● 개발용, QA용, 배포용 앱파일 분리
● 프리미엄 기능을 제공하는 유료, 무료버전 앱을 만들 수 있음.
● 코드는 같지만 리소스만 다른 앱을 만들 수 있음.
Create Build variants - Android
https://developer.android.com/studio/build/build-variants?hl=ko
● Gradle 스크립트를 수정하여flavor를 구분함.
● Flavor별 폴더를 별도로 생성, 리소스를 분리할 수 있음.
Create Build variants - Android
Create Build variants - Android
Create Build variants - Android
android {
...
defaultConfig {
...
+ dimension "build-type"
}
...
+ flavorDimensions "build-type"
+ productFlavors {
+ develop {
+ applicationIdSuffix = '.dev'
+ manifestPlaceholders = [appName: "deploy_automation_dev"]
+ }
+ product {
+ manifestPlaceholders = [appName: "deploy_automation"]
+ }
+ }
}
Create Build variants - Android
● Gradle 스크립트에 선언한 “appName” 매개변수를 manifest에 적용
● android / app / src / main / AndroidManifest.xml
https://developer.android.com/studio/build/manifest-build-variables?hl=ko
<application
android:label="${appName}"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
>
Create Build variants - iOS
https://docs.flutter.dev/deployment/flavors
● .xcconfig 파일 생성 후 scheme 을 설정함.
● iOS의 scheme 설정은 안드로이드의productFlavors 와 동일한 역할
● .xcconfig 파일은 productFlavors 에 정의된 빌드 구성정보와 동일.
Create Build variants - iOS
Create Build variants - iOS
Create Build variants - iOS
Create Build variants - iOS
Create Build variants - iOS
● Flutter의 빌드모드는 세가지
● Debug : 앱 개발 전용
● Release : 출시 전용
● Profile : 디버깅 전용
Create Build variants - iOS
https://docs.flutter.dev/testing/build-modes
Create Build variants - iOS
Create Build variants - iOS
Create Build variants - iOS
Create Build variants - iOS
Create Build variants - Flutter
● 빌드 유형에 따라 앱의 진입점을 변경.
● 진입점에 따라 API URL, Key 값이 변경됨.
Create Build variants - Flutter
enum BuildType { develop, product }
class AppConfigure {
static AppConfigure? _instance;
static AppConfigure get instance => _instance!;
final BuildType _buildType;
static BuildType get buildType => _instance!._buildType;
static String get apiURL {
switch (_instance?._buildType) {
case BuildType.develop:
return 'https://devapi.foo.bar';
case BuildType.product:
return 'https://api.foo.bar';
default:
return 'https://api.foo.bar';
}
}
AppConfigure._internal(this._buildType);
...
Create Build variants - Flutter
...
factory AppConfigure.newInstance(BuildType buildType) {
_instance ??= AppConfigure._internal(buildType);
return _instance!;
}
void run() {
runApp(
const MaterialApp(home: SettingPage()),
);
}
}
Create Build variants - Flutter
void main() => AppConfigure.newInstance(
BuildType.product,
).run();
void main() => AppConfigure.newInstance(
BuildType.develop,
).run();
lib/main.dart lib/main_dev.dart
Create Build variants - Flutter
class SettingPage extends StatelessWidget {
const SettingPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Setting Page')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Build Type: ${AppConfigure.buildType}'),
const SizedBox(height: 20),
Text('API URL: ${AppConfigure.apiURL}'),
],
)));
}
}
Create Build variants - Flutter
Create Build variants - Flutter
● flutter run --flavor develop -t lib/main_dev.dart
● flutter run --flavor production -t lib/main.dart
Continuous delivery with Flutter
https://docs.flutter.dev/deployment/cd#fastlane
What is Github Actions?
https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions
● 빌드, 테스트, 배포를 자동화할 수 있는 CI/CD 플랫폼.
● Github repo에 대한 push, pull request, tag 등 트리거로 만들 수 있음.
● 트리거가 동작하면YAML 기반 스크립트를 실행.
● fastlane과 마찬가지로 높은 수준으로 추상화된 액션을 제공함
● Docker build, cloud deploy, app build 등 다양한 작업 가능
What is Github Actions?
What is Github Actions?
name: learn-github-actions
run-name: ${{ github.actor }} is learning GitHub Actions
on: [push]
jobs:
check-bats-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '14'
- run: npm install -g bats
- run: bats -v
What is Github Actions?
name: learn-github-actions
run-name: ${{ github.actor }} is learning GitHub Actions
on: [push]
jobs:
check-bats-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '14'
- run: npm install -g bats
- run: bats -v
What is Github Actions?
name: learn-github-actions
run-name: ${{ github.actor }} is learning GitHub Actions
on: [push]
jobs:
check-bats-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '14'
- run: npm install -g bats
- run: bats -v
What is self-hosted runners?
● 작업을 실행하는 서버인 러너를 자체 호스팅으로 사용 가능.
● 특정 운영체제가 필요하거나 특별화된 하드웨어 구성이 필요한 경우 추천
● 단일 Repo 구성 가능
● 조직(organization) 단위로 구성하여 여러 Repo에서 구성 가능
● 엔터프라이즈계정의 경우 여러 조직 단위 구성 가능
● Linux, Windows, macOS 지원
● x64, ARM64, ARM32 지원
What is self-hosted runners?
● Github Pro 기준 한달에 3,000분을 무료
제공
● 빌드할 때 마다 환경설정 + 빌드 + 업로드
● 기본 작업시간 6분 * 10 = 60분 소모 시
한달에 무료 빌드 50번
● 30일 기준 하루에 6분 작업을 5번 사용 시
$72 ≈ 96500원
https://docs.github.com/en/actions/hosting-your-own-runners/adding-self-hosted-runners
What is fastlane?
https://docs.fastlane.tools/
● Ruby 기반의 오픈소스 자동화 도구
● 이미 만들어진 액션을 사용하여 스크립트를 작성
● 네이티브 빌드 지원 : Android, iOS
● 크로스플랫폼빌드 지원 : Flutter, React Native
What is fastlane?
https://docs.fastlane.tools/getting-started/ios/setup/
lane :beta do
increment_build_number
build_app
upload_to_testflight
end
lane :release do
capture_screenshots
build_app
upload_to_app_store # Upload the screenshots and the binary to iTunes
slack # Let your team-mates know the new version is live
end
$ fastlane release
Create Automation using fastlane
Create Automation using fastlane
Create Automation using fastlane
desc "Increase version number"
lane :increase_version_code do
version_code = number_of_commits(all: true)
yaml_file_path = "../../pubspec.yaml"
data = YAML.load_file(yaml_file_path)
version = data["version"]
version_name = data["version"].split("+")[0]
new_version_code = version_code + 1
new_version = "#{version_name}+#{new_build_number}"
data["version"] = new_version
File.open(yaml_file_path, 'w') { |f| YAML.dump(data, f) }
end
ios/fastlane/Fastfile && android/fastlane/Fastfile
fastlane actions : number_of_commits
https://docs.fastlane.tools/actions/number_of_commits/#number_of_commits
increment_build_number(build_number: number_of_commits)
build_number = number_of_commits(all: true)
increment_build_number(build_number: build_number)
● 현재 git 브랜치의 커밋 수를 반환.
● all 파라미터를 사용하여 현재 분기 대신 모든 커밋의 총 수를 반환.
Create Automation using fastlane
desc "Increase version number"
lane :increase_version_code do
version_code = number_of_commits(all: true)
yaml_file_path = "../../pubspec.yaml"
data = YAML.load_file(yaml_file_path)
version = data["version"]
version_name = data["version"].split("+")[0]
new_version_code = version_code + 1
new_version = "#{version_name}+#{new_build_number}"
data["version"] = new_version
File.open(yaml_file_path, 'w') { |f| YAML.dump(data, f) }
end
ios/fastlane/Fastfile && android/fastlane/Fastfile
Create Automation using fastlane
Create Automation using fastlane
desc "build develop"
lane :build_dev do |options|
## build APP
sh('flutter build apk -t lib/main_dev.dart --flavor develop')
end
android/fastlane/Fastfile
Create Automation using fastlane
desc "build develop"
lane :build_dev do |options|
## pods Repo Update
sh('flutter pub get')
cocoapods(
repo_update: true,
)
## build APP
build_app(
scheme: "develop",
clean: true,
workspace: "Runner.xcworkspace",
)
end
ios/fastlane/Fastfile
fastlane actions : cocoapods
https://docs.fastlane.tools/actions/cocoapods/#cocoapods
cocoapods(
clean_install: true,
podfile: "./CustomPodfile"
)
● pod install명령어를 실행
● 매개변수에 따라 캐시를 지우고 설치하거나 podfile 경로를 바꿀 수 있음.
Create Automation using fastlane
desc "build develop"
lane :build_dev do |options|
## pods Repo Update
sh('flutter pub get')
cocoapods(
repo_update: true,
use_bundle_exec: false,
)
## build APP
build_app(
scheme: "develop",
clean: true,
workspace: "Runner.xcworkspace",
)
end
ios/fastlane/Fastfile
fastlane actions : build_app
https://docs.fastlane.tools/actions/build_app/#build_app
build_app(
workspace: "MyApp.xcworkspace",
configuration: "Debug",
scheme: "MyApp",
silent: true,
clean: true,
output_directory: "path/to/dir", # Destination directory. Defaults to current directory.
output_name: "my-app.ipa", # specify the name of the .ipa file to generate (including file extension)
sdk: "iOS 11.1" # use SDK as the name or path of the base SDK when building the project.
)
● iOS 앱을 빌드하여 패키지를 만듦.
● 매개변수에 따라 다양한 빌드 옵션을 적용할 수 있음.
Create Automation using fastlane
Create Automation using fastlane
desc "Do Signing for Deploy iOS App"
lane :do_signing do |options|
api_key = app_store_connect_api_key(
key_id: options[:key_id],
issuer_id: options[:issuer_id],
key_content: options[:key_content],
is_key_content_base64: true
)
match(
type: 'appstore',
app_identifier: options[:app_identifier],
api_key: api_key,
readonly: true
)
end
ios/fastlane/Fastfile
fastlane actions : app_store_connect_api_key
https://docs.fastlane.tools/actions/app_store_connect_api_key
app_store_connect_api_key(
key_id: "D83848D23",
issuer_id: "227b0bbf-ada8-458c-9d62-3d8022b7d07f",
key_filepath: "D83848D23.p8"
)
● App Store Connect API를 활용.
● 이중인증 없음, 인증이 만료되지 않음.
fastlane actions : app_store_connect_api_key
https://appstoreconnect.apple.com/access/api
fastlane actions : app_store_connect_api_key
desc "Do Signing for Deploy iOS App"
lane :do_signing do |options|
api_key = app_store_connect_api_key(
key_id: options[:key_id],
issuer_id: options[:issuer_id],
key_content: options[:key_content],
is_key_content_base64: true
)
match(
type: 'appstore',
app_identifier: options[:app_identifier],
api_key: api_key,
readonly: true
)
end
ios/fastlane/Fastfile
Create Automation using fastlane
desc "Do Signing for Deploy iOS App"
lane :do_signing do |options|
api_key = app_store_connect_api_key(
key_id: options[:key_id],
issuer_id: options[:issuer_id],
key_content: options[:key_content],
is_key_content_base64: true
)
match(
type: 'appstore',
app_identifier: options[:app_identifier],
api_key: api_key,
readonly: true
)
end
ios/fastlane/Fastfile
fastlane actions : match
https://docs.fastlane.tools/actions/match
match(type: "appstore")
match(type: "development")
● git, google cloud, aws s3에 인증 관련 파일을 업로드
● 업로드 된 인증 관련 파일은 암호화되어 저장됨
● 각 lane에서 빌드하기 전 인증서를 대조하여 개발 환경에 상관없이 개발 및
배포 가능
fastlane actions : match
$ fastlane match init
[✔] 🚀
[12:21:30]: fastlane match supports multiple storage modes, please select the one you want to use:
1. git
2. google_cloud
3. s3
fastlane actions : match
[12:21:31]: Please create a new, private git repository to store the certificates and profiles there
[12:21:31]: URL of the Git Repo: [깃허브 repo 입력]
[12:21:34]: Successfully created './Matchfile'. You can open the file using a code editor.
[12:21:34]: You can now run `fastlane match development`, `fastlane match adhoc`, `fastlane match
enterprise` and `fastlane match appstore`
[12:21:34]: On the first run for each environment it will create the provisioning profiles and
[12:21:34]: certificates for you. From then on, it will automatically import the existing profiles.
[12:21:34]: For more information visit https://docs.fastlane.tools/actions/match/
fastlane actions : match
git_url("[깃허브 repo]")
storage_mode("git")
type("appstore") # The default type, can be: appstore, adhoc, enterprise or development
app_identifier(["[앱 아이디 입력]", "[앱 아이디 입력]"])
# username("user@fastlane.tools") # Your Apple Developer Portal username
# For all available options run `fastlane match --help`
# Remove the # in the beginning of the line to enable the other options
# The docs are available on https://docs.fastlane.tools/actions/match
fastlane actions : match
fastlane match nuke development
fastlane match nuke distribution
fastlane actions : match
fastlane match appstore
[✔] 🚀
[12:26:45]: Get started using a Gemfile for fastlane
https://docs.fastlane.tools/getting-started/ios/setup/#use-a-gemfile
+--------------+----------------------------------------------+
| Detected Values from './Matchfile' |
+--------------+----------------------------------------------+
| git_url | [깃허브 Repo] |
| storage_mode | git |
| type | appstore |
+--------------+----------------------------------------------+
...
fastlane actions : match
Create Automation using fastlane
desc "Do Signing for Deploy iOS App"
lane :do_signing do |options|
api_key = app_store_connect_api_key(
key_id: options[:key_id],
issuer_id: options[:issuer_id],
key_content: options[:key_content],
is_key_content_base64: true
)
match(
type: 'appstore',
app_identifier: options[:app_identifier],
api_key: api_key,
readonly: true
)
end
ios/fastlane/Fastfile
Create Automation using fastlane
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
...
android {
...
signingConfigs {
release {
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
}
}
...
https://docs.flutter.dev/deployment/android#signing-the-app
Create Automation using fastlane
Create Automation using fastlane
## deploy Develop APP
firebase_app_distribution(
app: "[firebase app id]",
firebase_cli_token: options[:firebase_token]
)
## deploy Product APP
upload_to_testflight(
skip_waiting_for_build_processing: true
)
ios/fastlane/Fastfile
fastlane actions : firebase_app_distribution
https://firebase.google.com/docs/app-distribution/ios/distribute-fastlane
firebase_app_distribution(
app: "1:123456789:ios:abcd1234",
testers: "tester1@company.com, tester2@company.com",
release_notes: "Lots of amazing new features to test out!"
)
● 신뢰할 수 있는 테스터에게 빠르게 배포
● 테스터 초대 및 관리 가능
Create Automation using fastlane
## deploy Develop APP
firebase_app_distribution(
app: "[firebase app id]",
firebase_cli_token: options[:firebase_token]
)
## deploy Product APP
upload_to_testflight(
skip_waiting_for_build_processing: true
)
ios/fastlane/Fastfile
fastlane actions : upload_to_testflight
https://docs.fastlane.tools/actions/upload_to_testflight
● 테스트 전용 앱인 testflight에 업로드
● 보통 testflight 업로드 이후 테스트 통과한 빌드파일을 appstore 출시에 올림
upload_to_testflight(skip_submission: true) # to only upload the build
upload_to_testflight(
beta_app_feedback_email: "email@email.com",
beta_app_description: "This is a description of my app",
demo_account_required: true,
notify_external_testers: false,
changelog: "This is my changelog of things that have changed in a log"
)
Create Automation using fastlane
## deploy Develop APP
firebase_app_distribution(
app: "[firebase app id]",
firebase_cli_token: options[:firebase_token]
)
## deploy Product APP
upload_to_play_store(
aab: "../build/app/outputs/bundle/productRelease/app-product-release.aab",
track: 'internal',
skip_upload_metadata: true,
)
android/fastlane/Fastfile
fastlane actions : upload_to_playstore
https://docs.fastlane.tools/actions/upload_to_play_store
● 스토어의 출시 단계를 나타내는 track
● flavor를 설정한 경우 app bundle 파일을 못찾아서 수동으로 잡아줘야함.
upload_to_playstore(
track: "internal",
abb: "../build/app/outputs/bundle/productRelease/app-product-release.aab",
skip_upload_metadata: true,
)
default_platform(:ios)
platform :ios do
lane :increase_version_code do
...
end
lane :do_signing do |options|
...
end
...
...
desc "Deploy develop ipa version to Firebase Distribution"
lane :build_deploy_dev do |options|
## signing
do_signing(
app_identifier: options[:app_identifier],
key_id: options[:key_id],
issuer_id: options[:issuer_id],
key_content: options[:key_content]
)
## pods Repo Update
sh('flutter pub get')
cocoapods(
repo_update: true,
use_bundle_exec: false,
)
## build APP
build_app(
scheme: "develop",
clean: true,
workspace: "Runner.xcworkspace",
)
## deploy APP
firebase_app_distribution(
app: "[firebase app id]",
firebase_cli_token: options[:firebase_token]
)
end
ios/fastlane/Fastfile
default_platform(:ios)
platform :ios do
lane :increase_version_code do
...
end
lane :do_signing do |options|
...
end
...
...
desc "Deploy a Product version to Apple App Store"
lane :build_deploy_prod do |options|
## signing
do_signing(
app_identifier: options[:app_identifier],
key_id: options[:key_id],
issuer_id: options[:issuer_id],
key_content: options[:key_content]
)
## pods Repo Update
sh('flutter pub get')
cocoapods(
repo_update: true,
use_bundle_exec: false,
)
## build APP
build_app(
scheme: "product",
clean: true,
workspace: "Runner.xcworkspace"
)
## deploy APP
upload_to_testflight(
skip_waiting_for_build_processing: true
)
end
ios/fastlane/Fastfile
default_platform(:android)
platform :android do
lane :increase_version_code do
...
end
...
...
desc "Deploy develop version apk to Firebase Distribution"
lane :build_deploy_dev do |options|
## build APP
sh('flutter build apk -t lib/src/config/env/env_develop.dart
--flavor develop')
## deploy APP
firebase_app_distribution(
app: "[firebase app id]",
apk_path:
"../build/app/outputs/flutter-apk/app-develop-release.apk",
firebase_cli_token: options[:firebase_token]
)
end
android/fastlane/Fastfile
default_platform(:android)
platform :android do
lane :increase_version_code do
...
end
...
...
desc "Deploy a Product version to Google Play Store"
lane :build_deploy_prod do
## build APP
sh("flutter build appbundle --release -t
lib/src/config/env/env_product.dart --flavor product")
## deploy APP
upload_to_play_store(
aab:
"../build/app/outputs/bundle/productRelease/app-product-release.
aab",
track: 'internal',
skip_upload_metadata: true,
)
end
android/fastlane/Fastfile
Configure Github Actions
Configure Github Actions
Configure Github Actions
name: Deploy product to testflight, playstore internal track
on:
push:
branches: [ main ]
jobs:
deploy_ios:
runs-on: macOS
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Increase Version Code
run: fastlane increase_version_code
working-directory: ios
Configure Github Actions
- name: Deploy Product to Store
run: fastlane build_deploy_prod app_identifier:[앱 아이디] key_id:${{ secrets.ASCAPI_KEY_ID }} issuer_id:${{
secrets.ASCAPI_ISSUER_ID }} key_content:${{ secrets.ASCAPI_KEY_CONTENT }}
working-directory: ios
## signing
do_signing(
app_identifier: options[:app_identifier],
key_id: options[:key_id],
issuer_id: options[:issuer_id],
key_content: options[:key_content]
)
Configure Github Actions
deploy_android:
runs-on: macOS
needs: [deploy_ios]
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Increase Version Code
run: fastlane increment_version_code
working-directory: android
- name: Generate Android keystore
id: android_keystore
uses: timheuer/base64-to-file@v1.1
with:
fileName: upload-keystore.jks
encodedString: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
Configure Github Actions
- name: Create key.properties
run: |
echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > android/key.properties
echo "storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >> android/key.properties
echo "keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}" >> android/key.properties
echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" >> android/key.properties
- name: Deploy Product to Store
run: fastlane build_deploy_prod
working-directory: android
signingConfigs {
release {
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
}
}
Configure Github Actions
name: Deploy product to testflight, playstore internal track
on:
push:
branches: [ main ]
jobs:
deploy_ios:
runs-on: macOS
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Increase Version Code
run: fastlane increment_version_code
working-directory: ios
- name: Deploy Product to Store
run: fastlane build_deploy_prod app_identifier:[앱
아이디] key_id:${{ secrets.ASCAPI_KEY_ID }} issuer_id:${{
secrets.ASCAPI_ISSUER_ID }} key_content:${{
secrets.ASCAPI_KEY_CONTENT }}
working-directory: ios
deploy_android:
runs-on: macOS
needs: [deploy_ios]
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Increase Version Code
run: fastlane increment_version_code
working-directory: android
- name: Generate Android keystore
id: android_keystore
uses: timheuer/base64-to-file@v1.1
with:
fileName: upload-keystore.jks
encodedString: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
- name: Create key.properties
run: |
echo "storeFile=${{
steps.android_keystore.outputs.filePath }}" >
android/key.properties
echo "storePassword=${{
secrets.ANDROID_KEYSTORE_PASSWORD }}" >> android/key.properties
echo "keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}"
>> android/key.properties
echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" >>
android/key.properties
- name: Deploy Product to Store
run: fastlane build_deploy_prod
working-directory: android
.github/workflows/deploy_main.yaml
https://gdgsongdo.page.link/fluttercode
[Flutter] Code in Songdo 모집!
멘토와 함께 자신의 아이디어를 Flutter로 직접
개발해보세요
Thank you 😁

More Related Content

Similar to DevFest 2022 - GitHub Actions를 활용한 Flutter 배포 자동화하기

Fastlane - Automation and Continuous Delivery for iOS Apps
Fastlane - Automation and Continuous Delivery for iOS AppsFastlane - Automation and Continuous Delivery for iOS Apps
Fastlane - Automation and Continuous Delivery for iOS AppsSarath C
 
An OpenShift Primer for Developers to get your Code into the Cloud (PTJUG)
An OpenShift Primer for Developers to get your Code into the Cloud (PTJUG)An OpenShift Primer for Developers to get your Code into the Cloud (PTJUG)
An OpenShift Primer for Developers to get your Code into the Cloud (PTJUG)Eric D. Schabell
 
Essential Tools for Modern PHP
Essential Tools for Modern PHPEssential Tools for Modern PHP
Essential Tools for Modern PHPAlex Weissman
 
Developing cross platform apps in Flutter (Android, iOS, and Web)
Developing cross platform apps in Flutter (Android, iOS, and Web)Developing cross platform apps in Flutter (Android, iOS, and Web)
Developing cross platform apps in Flutter (Android, iOS, and Web)Priyanka Tyagi
 
Build Great Networked APIs with Swift, OpenAPI, and gRPC
Build Great Networked APIs with Swift, OpenAPI, and gRPCBuild Great Networked APIs with Swift, OpenAPI, and gRPC
Build Great Networked APIs with Swift, OpenAPI, and gRPCTim Burks
 
Gitlab and Lingvokot
Gitlab and LingvokotGitlab and Lingvokot
Gitlab and LingvokotLingvokot
 
DevFest 2022 - Cloud Workstation Introduction TaiChung
DevFest 2022 - Cloud Workstation Introduction TaiChungDevFest 2022 - Cloud Workstation Introduction TaiChung
DevFest 2022 - Cloud Workstation Introduction TaiChungKAI CHU CHUNG
 
Distributing UI Libraries: in a post Web-Component world
Distributing UI Libraries: in a post Web-Component worldDistributing UI Libraries: in a post Web-Component world
Distributing UI Libraries: in a post Web-Component worldRachael L Moore
 
Reactive application using meteor
Reactive application using meteorReactive application using meteor
Reactive application using meteorSapna Upreti
 
Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...
Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...
Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...Shift Conference
 
Toolbox of a Ruby Team
Toolbox of a Ruby TeamToolbox of a Ruby Team
Toolbox of a Ruby TeamArto Artnik
 
Front End Development for Back End Developers - UberConf 2017
Front End Development for Back End Developers - UberConf 2017Front End Development for Back End Developers - UberConf 2017
Front End Development for Back End Developers - UberConf 2017Matt Raible
 
GoogleDSC_ GHRCE_ flutter_firebase.pptx
GoogleDSC_ GHRCE_  flutter_firebase.pptxGoogleDSC_ GHRCE_  flutter_firebase.pptx
GoogleDSC_ GHRCE_ flutter_firebase.pptxGoogleDeveloperStude22
 
Intro to Github Actions @likecoin
Intro to Github Actions @likecoinIntro to Github Actions @likecoin
Intro to Github Actions @likecoinWilliam Chong
 
Scaling up development of a modular code base
Scaling up development of a modular code baseScaling up development of a modular code base
Scaling up development of a modular code baseRobert Munteanu
 
Scaling up development of a modular code base - R Munteanu
Scaling up development of a modular code base - R MunteanuScaling up development of a modular code base - R Munteanu
Scaling up development of a modular code base - R Munteanumfrancis
 
Using the new WordPress REST API
Using the new WordPress REST APIUsing the new WordPress REST API
Using the new WordPress REST APICaldera Labs
 
CloudLand 2023: Rock, Paper, Scissors Cloud Competition - Go vs. Java
CloudLand 2023: Rock, Paper, Scissors Cloud Competition - Go vs. JavaCloudLand 2023: Rock, Paper, Scissors Cloud Competition - Go vs. Java
CloudLand 2023: Rock, Paper, Scissors Cloud Competition - Go vs. JavaJan Stamer
 

Similar to DevFest 2022 - GitHub Actions를 활용한 Flutter 배포 자동화하기 (20)

Fastlane - Automation and Continuous Delivery for iOS Apps
Fastlane - Automation and Continuous Delivery for iOS AppsFastlane - Automation and Continuous Delivery for iOS Apps
Fastlane - Automation and Continuous Delivery for iOS Apps
 
An OpenShift Primer for Developers to get your Code into the Cloud (PTJUG)
An OpenShift Primer for Developers to get your Code into the Cloud (PTJUG)An OpenShift Primer for Developers to get your Code into the Cloud (PTJUG)
An OpenShift Primer for Developers to get your Code into the Cloud (PTJUG)
 
Essential Tools for Modern PHP
Essential Tools for Modern PHPEssential Tools for Modern PHP
Essential Tools for Modern PHP
 
Developing cross platform apps in Flutter (Android, iOS, and Web)
Developing cross platform apps in Flutter (Android, iOS, and Web)Developing cross platform apps in Flutter (Android, iOS, and Web)
Developing cross platform apps in Flutter (Android, iOS, and Web)
 
Build Great Networked APIs with Swift, OpenAPI, and gRPC
Build Great Networked APIs with Swift, OpenAPI, and gRPCBuild Great Networked APIs with Swift, OpenAPI, and gRPC
Build Great Networked APIs with Swift, OpenAPI, and gRPC
 
Gitlab and Lingvokot
Gitlab and LingvokotGitlab and Lingvokot
Gitlab and Lingvokot
 
DevFest 2022 - Cloud Workstation Introduction TaiChung
DevFest 2022 - Cloud Workstation Introduction TaiChungDevFest 2022 - Cloud Workstation Introduction TaiChung
DevFest 2022 - Cloud Workstation Introduction TaiChung
 
Distributing UI Libraries: in a post Web-Component world
Distributing UI Libraries: in a post Web-Component worldDistributing UI Libraries: in a post Web-Component world
Distributing UI Libraries: in a post Web-Component world
 
Reactive application using meteor
Reactive application using meteorReactive application using meteor
Reactive application using meteor
 
Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...
Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...
Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...
 
Toolbox of a Ruby Team
Toolbox of a Ruby TeamToolbox of a Ruby Team
Toolbox of a Ruby Team
 
Griffon Presentation
Griffon PresentationGriffon Presentation
Griffon Presentation
 
Front End Development for Back End Developers - UberConf 2017
Front End Development for Back End Developers - UberConf 2017Front End Development for Back End Developers - UberConf 2017
Front End Development for Back End Developers - UberConf 2017
 
GoogleDSC_ GHRCE_ flutter_firebase.pptx
GoogleDSC_ GHRCE_  flutter_firebase.pptxGoogleDSC_ GHRCE_  flutter_firebase.pptx
GoogleDSC_ GHRCE_ flutter_firebase.pptx
 
Intro to Github Actions @likecoin
Intro to Github Actions @likecoinIntro to Github Actions @likecoin
Intro to Github Actions @likecoin
 
Scaling up development of a modular code base
Scaling up development of a modular code baseScaling up development of a modular code base
Scaling up development of a modular code base
 
Scaling up development of a modular code base - R Munteanu
Scaling up development of a modular code base - R MunteanuScaling up development of a modular code base - R Munteanu
Scaling up development of a modular code base - R Munteanu
 
Using the new WordPress REST API
Using the new WordPress REST APIUsing the new WordPress REST API
Using the new WordPress REST API
 
Sst hackathon express
Sst hackathon expressSst hackathon express
Sst hackathon express
 
CloudLand 2023: Rock, Paper, Scissors Cloud Competition - Go vs. Java
CloudLand 2023: Rock, Paper, Scissors Cloud Competition - Go vs. JavaCloudLand 2023: Rock, Paper, Scissors Cloud Competition - Go vs. Java
CloudLand 2023: Rock, Paper, Scissors Cloud Competition - Go vs. Java
 

DevFest 2022 - GitHub Actions를 활용한 Flutter 배포 자동화하기

  • 1. Songdo GitHub Actions를 활용한 Flutter 배포 자동화하기 SuJang Yang Tech Lead, @bluefrog
  • 2. GDG Songdo Organizer (2020~ ) Tech Lead @bluefrog (2020 ~ ) - Android - Flutter - Back-end + DevOps I’m an App Developer. yangsterchief@duck.com https://github.com/yangster-chief
  • 3. 이런 분들에게 추천합니다 👍 1. Android, iOS 배포 자동화에 관심있으신 분들 2. Flavor 를 활용한 개발환경, 배포환경을 구분하여 배포하는 방법에 관심있으신 분들 3. 초기 구축 비용 때문에Jenkins 같은 자동화 도구 도입을 고민하는 분들
  • 7. App Signing in Android
  • 12. Build variants in Flutter https://docs.flutter.dev/deployment/flavors ● 하나의 프로젝트 환경에서 빌드 결과물을 물리적으로 분리 ● 개발용, QA용, 배포용 앱파일 분리 ● 프리미엄 기능을 제공하는 유료, 무료버전 앱을 만들 수 있음. ● 코드는 같지만 리소스만 다른 앱을 만들 수 있음.
  • 13. Create Build variants - Android https://developer.android.com/studio/build/build-variants?hl=ko ● Gradle 스크립트를 수정하여flavor를 구분함. ● Flavor별 폴더를 별도로 생성, 리소스를 분리할 수 있음.
  • 16. Create Build variants - Android android { ... defaultConfig { ... + dimension "build-type" } ... + flavorDimensions "build-type" + productFlavors { + develop { + applicationIdSuffix = '.dev' + manifestPlaceholders = [appName: "deploy_automation_dev"] + } + product { + manifestPlaceholders = [appName: "deploy_automation"] + } + } }
  • 17. Create Build variants - Android ● Gradle 스크립트에 선언한 “appName” 매개변수를 manifest에 적용 ● android / app / src / main / AndroidManifest.xml https://developer.android.com/studio/build/manifest-build-variables?hl=ko <application android:label="${appName}" android:name="${applicationName}" android:icon="@mipmap/ic_launcher" >
  • 18. Create Build variants - iOS https://docs.flutter.dev/deployment/flavors ● .xcconfig 파일 생성 후 scheme 을 설정함. ● iOS의 scheme 설정은 안드로이드의productFlavors 와 동일한 역할 ● .xcconfig 파일은 productFlavors 에 정의된 빌드 구성정보와 동일.
  • 24. ● Flutter의 빌드모드는 세가지 ● Debug : 앱 개발 전용 ● Release : 출시 전용 ● Profile : 디버깅 전용 Create Build variants - iOS https://docs.flutter.dev/testing/build-modes
  • 29. Create Build variants - Flutter ● 빌드 유형에 따라 앱의 진입점을 변경. ● 진입점에 따라 API URL, Key 값이 변경됨.
  • 30. Create Build variants - Flutter enum BuildType { develop, product } class AppConfigure { static AppConfigure? _instance; static AppConfigure get instance => _instance!; final BuildType _buildType; static BuildType get buildType => _instance!._buildType; static String get apiURL { switch (_instance?._buildType) { case BuildType.develop: return 'https://devapi.foo.bar'; case BuildType.product: return 'https://api.foo.bar'; default: return 'https://api.foo.bar'; } } AppConfigure._internal(this._buildType); ...
  • 31. Create Build variants - Flutter ... factory AppConfigure.newInstance(BuildType buildType) { _instance ??= AppConfigure._internal(buildType); return _instance!; } void run() { runApp( const MaterialApp(home: SettingPage()), ); } }
  • 32. Create Build variants - Flutter void main() => AppConfigure.newInstance( BuildType.product, ).run(); void main() => AppConfigure.newInstance( BuildType.develop, ).run(); lib/main.dart lib/main_dev.dart
  • 33. Create Build variants - Flutter class SettingPage extends StatelessWidget { const SettingPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Setting Page')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('Build Type: ${AppConfigure.buildType}'), const SizedBox(height: 20), Text('API URL: ${AppConfigure.apiURL}'), ], ))); } }
  • 35. Create Build variants - Flutter ● flutter run --flavor develop -t lib/main_dev.dart ● flutter run --flavor production -t lib/main.dart
  • 36. Continuous delivery with Flutter https://docs.flutter.dev/deployment/cd#fastlane
  • 37. What is Github Actions? https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions ● 빌드, 테스트, 배포를 자동화할 수 있는 CI/CD 플랫폼. ● Github repo에 대한 push, pull request, tag 등 트리거로 만들 수 있음. ● 트리거가 동작하면YAML 기반 스크립트를 실행. ● fastlane과 마찬가지로 높은 수준으로 추상화된 액션을 제공함 ● Docker build, cloud deploy, app build 등 다양한 작업 가능
  • 38. What is Github Actions?
  • 39. What is Github Actions? name: learn-github-actions run-name: ${{ github.actor }} is learning GitHub Actions on: [push] jobs: check-bats-version: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '14' - run: npm install -g bats - run: bats -v
  • 40. What is Github Actions? name: learn-github-actions run-name: ${{ github.actor }} is learning GitHub Actions on: [push] jobs: check-bats-version: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '14' - run: npm install -g bats - run: bats -v
  • 41. What is Github Actions? name: learn-github-actions run-name: ${{ github.actor }} is learning GitHub Actions on: [push] jobs: check-bats-version: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '14' - run: npm install -g bats - run: bats -v
  • 42. What is self-hosted runners? ● 작업을 실행하는 서버인 러너를 자체 호스팅으로 사용 가능. ● 특정 운영체제가 필요하거나 특별화된 하드웨어 구성이 필요한 경우 추천 ● 단일 Repo 구성 가능 ● 조직(organization) 단위로 구성하여 여러 Repo에서 구성 가능 ● 엔터프라이즈계정의 경우 여러 조직 단위 구성 가능 ● Linux, Windows, macOS 지원 ● x64, ARM64, ARM32 지원
  • 43. What is self-hosted runners? ● Github Pro 기준 한달에 3,000분을 무료 제공 ● 빌드할 때 마다 환경설정 + 빌드 + 업로드 ● 기본 작업시간 6분 * 10 = 60분 소모 시 한달에 무료 빌드 50번 ● 30일 기준 하루에 6분 작업을 5번 사용 시 $72 ≈ 96500원 https://docs.github.com/en/actions/hosting-your-own-runners/adding-self-hosted-runners
  • 44. What is fastlane? https://docs.fastlane.tools/ ● Ruby 기반의 오픈소스 자동화 도구 ● 이미 만들어진 액션을 사용하여 스크립트를 작성 ● 네이티브 빌드 지원 : Android, iOS ● 크로스플랫폼빌드 지원 : Flutter, React Native
  • 45. What is fastlane? https://docs.fastlane.tools/getting-started/ios/setup/ lane :beta do increment_build_number build_app upload_to_testflight end lane :release do capture_screenshots build_app upload_to_app_store # Upload the screenshots and the binary to iTunes slack # Let your team-mates know the new version is live end $ fastlane release
  • 48. Create Automation using fastlane desc "Increase version number" lane :increase_version_code do version_code = number_of_commits(all: true) yaml_file_path = "../../pubspec.yaml" data = YAML.load_file(yaml_file_path) version = data["version"] version_name = data["version"].split("+")[0] new_version_code = version_code + 1 new_version = "#{version_name}+#{new_build_number}" data["version"] = new_version File.open(yaml_file_path, 'w') { |f| YAML.dump(data, f) } end ios/fastlane/Fastfile && android/fastlane/Fastfile
  • 49. fastlane actions : number_of_commits https://docs.fastlane.tools/actions/number_of_commits/#number_of_commits increment_build_number(build_number: number_of_commits) build_number = number_of_commits(all: true) increment_build_number(build_number: build_number) ● 현재 git 브랜치의 커밋 수를 반환. ● all 파라미터를 사용하여 현재 분기 대신 모든 커밋의 총 수를 반환.
  • 50. Create Automation using fastlane desc "Increase version number" lane :increase_version_code do version_code = number_of_commits(all: true) yaml_file_path = "../../pubspec.yaml" data = YAML.load_file(yaml_file_path) version = data["version"] version_name = data["version"].split("+")[0] new_version_code = version_code + 1 new_version = "#{version_name}+#{new_build_number}" data["version"] = new_version File.open(yaml_file_path, 'w') { |f| YAML.dump(data, f) } end ios/fastlane/Fastfile && android/fastlane/Fastfile
  • 52. Create Automation using fastlane desc "build develop" lane :build_dev do |options| ## build APP sh('flutter build apk -t lib/main_dev.dart --flavor develop') end android/fastlane/Fastfile
  • 53. Create Automation using fastlane desc "build develop" lane :build_dev do |options| ## pods Repo Update sh('flutter pub get') cocoapods( repo_update: true, ) ## build APP build_app( scheme: "develop", clean: true, workspace: "Runner.xcworkspace", ) end ios/fastlane/Fastfile
  • 54. fastlane actions : cocoapods https://docs.fastlane.tools/actions/cocoapods/#cocoapods cocoapods( clean_install: true, podfile: "./CustomPodfile" ) ● pod install명령어를 실행 ● 매개변수에 따라 캐시를 지우고 설치하거나 podfile 경로를 바꿀 수 있음.
  • 55. Create Automation using fastlane desc "build develop" lane :build_dev do |options| ## pods Repo Update sh('flutter pub get') cocoapods( repo_update: true, use_bundle_exec: false, ) ## build APP build_app( scheme: "develop", clean: true, workspace: "Runner.xcworkspace", ) end ios/fastlane/Fastfile
  • 56. fastlane actions : build_app https://docs.fastlane.tools/actions/build_app/#build_app build_app( workspace: "MyApp.xcworkspace", configuration: "Debug", scheme: "MyApp", silent: true, clean: true, output_directory: "path/to/dir", # Destination directory. Defaults to current directory. output_name: "my-app.ipa", # specify the name of the .ipa file to generate (including file extension) sdk: "iOS 11.1" # use SDK as the name or path of the base SDK when building the project. ) ● iOS 앱을 빌드하여 패키지를 만듦. ● 매개변수에 따라 다양한 빌드 옵션을 적용할 수 있음.
  • 58. Create Automation using fastlane desc "Do Signing for Deploy iOS App" lane :do_signing do |options| api_key = app_store_connect_api_key( key_id: options[:key_id], issuer_id: options[:issuer_id], key_content: options[:key_content], is_key_content_base64: true ) match( type: 'appstore', app_identifier: options[:app_identifier], api_key: api_key, readonly: true ) end ios/fastlane/Fastfile
  • 59. fastlane actions : app_store_connect_api_key https://docs.fastlane.tools/actions/app_store_connect_api_key app_store_connect_api_key( key_id: "D83848D23", issuer_id: "227b0bbf-ada8-458c-9d62-3d8022b7d07f", key_filepath: "D83848D23.p8" ) ● App Store Connect API를 활용. ● 이중인증 없음, 인증이 만료되지 않음.
  • 60. fastlane actions : app_store_connect_api_key https://appstoreconnect.apple.com/access/api
  • 61. fastlane actions : app_store_connect_api_key desc "Do Signing for Deploy iOS App" lane :do_signing do |options| api_key = app_store_connect_api_key( key_id: options[:key_id], issuer_id: options[:issuer_id], key_content: options[:key_content], is_key_content_base64: true ) match( type: 'appstore', app_identifier: options[:app_identifier], api_key: api_key, readonly: true ) end ios/fastlane/Fastfile
  • 62. Create Automation using fastlane desc "Do Signing for Deploy iOS App" lane :do_signing do |options| api_key = app_store_connect_api_key( key_id: options[:key_id], issuer_id: options[:issuer_id], key_content: options[:key_content], is_key_content_base64: true ) match( type: 'appstore', app_identifier: options[:app_identifier], api_key: api_key, readonly: true ) end ios/fastlane/Fastfile
  • 63. fastlane actions : match https://docs.fastlane.tools/actions/match match(type: "appstore") match(type: "development") ● git, google cloud, aws s3에 인증 관련 파일을 업로드 ● 업로드 된 인증 관련 파일은 암호화되어 저장됨 ● 각 lane에서 빌드하기 전 인증서를 대조하여 개발 환경에 상관없이 개발 및 배포 가능
  • 64. fastlane actions : match $ fastlane match init [✔] 🚀 [12:21:30]: fastlane match supports multiple storage modes, please select the one you want to use: 1. git 2. google_cloud 3. s3
  • 65. fastlane actions : match [12:21:31]: Please create a new, private git repository to store the certificates and profiles there [12:21:31]: URL of the Git Repo: [깃허브 repo 입력] [12:21:34]: Successfully created './Matchfile'. You can open the file using a code editor. [12:21:34]: You can now run `fastlane match development`, `fastlane match adhoc`, `fastlane match enterprise` and `fastlane match appstore` [12:21:34]: On the first run for each environment it will create the provisioning profiles and [12:21:34]: certificates for you. From then on, it will automatically import the existing profiles. [12:21:34]: For more information visit https://docs.fastlane.tools/actions/match/
  • 66. fastlane actions : match git_url("[깃허브 repo]") storage_mode("git") type("appstore") # The default type, can be: appstore, adhoc, enterprise or development app_identifier(["[앱 아이디 입력]", "[앱 아이디 입력]"]) # username("user@fastlane.tools") # Your Apple Developer Portal username # For all available options run `fastlane match --help` # Remove the # in the beginning of the line to enable the other options # The docs are available on https://docs.fastlane.tools/actions/match
  • 67. fastlane actions : match fastlane match nuke development fastlane match nuke distribution
  • 68. fastlane actions : match fastlane match appstore [✔] 🚀 [12:26:45]: Get started using a Gemfile for fastlane https://docs.fastlane.tools/getting-started/ios/setup/#use-a-gemfile +--------------+----------------------------------------------+ | Detected Values from './Matchfile' | +--------------+----------------------------------------------+ | git_url | [깃허브 Repo] | | storage_mode | git | | type | appstore | +--------------+----------------------------------------------+ ...
  • 70. Create Automation using fastlane desc "Do Signing for Deploy iOS App" lane :do_signing do |options| api_key = app_store_connect_api_key( key_id: options[:key_id], issuer_id: options[:issuer_id], key_content: options[:key_content], is_key_content_base64: true ) match( type: 'appstore', app_identifier: options[:app_identifier], api_key: api_key, readonly: true ) end ios/fastlane/Fastfile
  • 71. Create Automation using fastlane def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } ... android { ... signingConfigs { release { storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null storePassword keystoreProperties['storePassword'] keyAlias keystoreProperties['keyAlias'] keyPassword keystoreProperties['keyPassword'] } } ... https://docs.flutter.dev/deployment/android#signing-the-app
  • 73. Create Automation using fastlane ## deploy Develop APP firebase_app_distribution( app: "[firebase app id]", firebase_cli_token: options[:firebase_token] ) ## deploy Product APP upload_to_testflight( skip_waiting_for_build_processing: true ) ios/fastlane/Fastfile
  • 74. fastlane actions : firebase_app_distribution https://firebase.google.com/docs/app-distribution/ios/distribute-fastlane firebase_app_distribution( app: "1:123456789:ios:abcd1234", testers: "tester1@company.com, tester2@company.com", release_notes: "Lots of amazing new features to test out!" ) ● 신뢰할 수 있는 테스터에게 빠르게 배포 ● 테스터 초대 및 관리 가능
  • 75. Create Automation using fastlane ## deploy Develop APP firebase_app_distribution( app: "[firebase app id]", firebase_cli_token: options[:firebase_token] ) ## deploy Product APP upload_to_testflight( skip_waiting_for_build_processing: true ) ios/fastlane/Fastfile
  • 76. fastlane actions : upload_to_testflight https://docs.fastlane.tools/actions/upload_to_testflight ● 테스트 전용 앱인 testflight에 업로드 ● 보통 testflight 업로드 이후 테스트 통과한 빌드파일을 appstore 출시에 올림 upload_to_testflight(skip_submission: true) # to only upload the build upload_to_testflight( beta_app_feedback_email: "email@email.com", beta_app_description: "This is a description of my app", demo_account_required: true, notify_external_testers: false, changelog: "This is my changelog of things that have changed in a log" )
  • 77. Create Automation using fastlane ## deploy Develop APP firebase_app_distribution( app: "[firebase app id]", firebase_cli_token: options[:firebase_token] ) ## deploy Product APP upload_to_play_store( aab: "../build/app/outputs/bundle/productRelease/app-product-release.aab", track: 'internal', skip_upload_metadata: true, ) android/fastlane/Fastfile
  • 78. fastlane actions : upload_to_playstore https://docs.fastlane.tools/actions/upload_to_play_store ● 스토어의 출시 단계를 나타내는 track ● flavor를 설정한 경우 app bundle 파일을 못찾아서 수동으로 잡아줘야함. upload_to_playstore( track: "internal", abb: "../build/app/outputs/bundle/productRelease/app-product-release.aab", skip_upload_metadata: true, )
  • 79. default_platform(:ios) platform :ios do lane :increase_version_code do ... end lane :do_signing do |options| ... end ... ... desc "Deploy develop ipa version to Firebase Distribution" lane :build_deploy_dev do |options| ## signing do_signing( app_identifier: options[:app_identifier], key_id: options[:key_id], issuer_id: options[:issuer_id], key_content: options[:key_content] ) ## pods Repo Update sh('flutter pub get') cocoapods( repo_update: true, use_bundle_exec: false, ) ## build APP build_app( scheme: "develop", clean: true, workspace: "Runner.xcworkspace", ) ## deploy APP firebase_app_distribution( app: "[firebase app id]", firebase_cli_token: options[:firebase_token] ) end ios/fastlane/Fastfile
  • 80. default_platform(:ios) platform :ios do lane :increase_version_code do ... end lane :do_signing do |options| ... end ... ... desc "Deploy a Product version to Apple App Store" lane :build_deploy_prod do |options| ## signing do_signing( app_identifier: options[:app_identifier], key_id: options[:key_id], issuer_id: options[:issuer_id], key_content: options[:key_content] ) ## pods Repo Update sh('flutter pub get') cocoapods( repo_update: true, use_bundle_exec: false, ) ## build APP build_app( scheme: "product", clean: true, workspace: "Runner.xcworkspace" ) ## deploy APP upload_to_testflight( skip_waiting_for_build_processing: true ) end ios/fastlane/Fastfile
  • 81. default_platform(:android) platform :android do lane :increase_version_code do ... end ... ... desc "Deploy develop version apk to Firebase Distribution" lane :build_deploy_dev do |options| ## build APP sh('flutter build apk -t lib/src/config/env/env_develop.dart --flavor develop') ## deploy APP firebase_app_distribution( app: "[firebase app id]", apk_path: "../build/app/outputs/flutter-apk/app-develop-release.apk", firebase_cli_token: options[:firebase_token] ) end android/fastlane/Fastfile
  • 82. default_platform(:android) platform :android do lane :increase_version_code do ... end ... ... desc "Deploy a Product version to Google Play Store" lane :build_deploy_prod do ## build APP sh("flutter build appbundle --release -t lib/src/config/env/env_product.dart --flavor product") ## deploy APP upload_to_play_store( aab: "../build/app/outputs/bundle/productRelease/app-product-release. aab", track: 'internal', skip_upload_metadata: true, ) end android/fastlane/Fastfile
  • 85. Configure Github Actions name: Deploy product to testflight, playstore internal track on: push: branches: [ main ] jobs: deploy_ios: runs-on: macOS steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Increase Version Code run: fastlane increase_version_code working-directory: ios
  • 86. Configure Github Actions - name: Deploy Product to Store run: fastlane build_deploy_prod app_identifier:[앱 아이디] key_id:${{ secrets.ASCAPI_KEY_ID }} issuer_id:${{ secrets.ASCAPI_ISSUER_ID }} key_content:${{ secrets.ASCAPI_KEY_CONTENT }} working-directory: ios ## signing do_signing( app_identifier: options[:app_identifier], key_id: options[:key_id], issuer_id: options[:issuer_id], key_content: options[:key_content] )
  • 87. Configure Github Actions deploy_android: runs-on: macOS needs: [deploy_ios] steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Increase Version Code run: fastlane increment_version_code working-directory: android - name: Generate Android keystore id: android_keystore uses: timheuer/base64-to-file@v1.1 with: fileName: upload-keystore.jks encodedString: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
  • 88. Configure Github Actions - name: Create key.properties run: | echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > android/key.properties echo "storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >> android/key.properties echo "keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}" >> android/key.properties echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" >> android/key.properties - name: Deploy Product to Store run: fastlane build_deploy_prod working-directory: android signingConfigs { release { storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null storePassword keystoreProperties['storePassword'] keyAlias keystoreProperties['keyAlias'] keyPassword keystoreProperties['keyPassword'] } }
  • 90. name: Deploy product to testflight, playstore internal track on: push: branches: [ main ] jobs: deploy_ios: runs-on: macOS steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Increase Version Code run: fastlane increment_version_code working-directory: ios - name: Deploy Product to Store run: fastlane build_deploy_prod app_identifier:[앱 아이디] key_id:${{ secrets.ASCAPI_KEY_ID }} issuer_id:${{ secrets.ASCAPI_ISSUER_ID }} key_content:${{ secrets.ASCAPI_KEY_CONTENT }} working-directory: ios deploy_android: runs-on: macOS needs: [deploy_ios] steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Increase Version Code run: fastlane increment_version_code working-directory: android - name: Generate Android keystore id: android_keystore uses: timheuer/base64-to-file@v1.1 with: fileName: upload-keystore.jks encodedString: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} - name: Create key.properties run: | echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > android/key.properties echo "storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >> android/key.properties echo "keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}" >> android/key.properties echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" >> android/key.properties - name: Deploy Product to Store run: fastlane build_deploy_prod working-directory: android .github/workflows/deploy_main.yaml
  • 91. https://gdgsongdo.page.link/fluttercode [Flutter] Code in Songdo 모집! 멘토와 함께 자신의 아이디어를 Flutter로 직접 개발해보세요