วิธีการใช้ Fastlane บน React-Native แบบย่อ

Kobkrit Viriyayudhakorn
iApp
Published in
6 min readMar 20, 2017

วิธีการลง Fastlane

$ brew install ruby
$ gem install fastlane

Fastlane ทำอะไรได้บ้าง

Fastlane คือ Tools ที่ใช้ส่ง App ไปยัง Apple Store และ Play Store ผ่าน Command line ทำให้ชีวิต Dev ของพวกเราง่ายกว่าเดิมเยอะ

มี Tools เด็ดๆมากมาย ในการส่ง Builds อาทิเช่น

ตระกูล iOS
ในการ Test Flight และ Android

นอกจากนี้ Fastlane ยังแยก Credential ในการส่ง App ออกมาต่างหาก จากเดิมที่เป็นการผูกไว้บนเครื่องใดเครื่องนึง ออกมาทำให้อยู่ในรูปของ Private Git Repo ซึ่งสามารถแชร์ให้กับทีมพัฒนาสามารถใช้ร่วมกันได้ (สามารถทำให้ใครในทีม ส่งแอพขึ้นมาได้)

(Password Protected) Fastlane iApp’s iOS Private Git Repo and Android Key https://docs.google.com/document/d/1A7X-u6ZwXaumqgRLqvrXGtrz9X2A2KgkbQyJwd3MYOA/edit#

การใช้ Fastlane ใน iApp

ทุก Proj ที่เป็น React-Native พี่ซีจะเป็นคน Setup Fastlane ในแต่ละ Proj ให้ ทุกอย่างจะถูกเก็บไว้ใน Folder /fastlane/ การ Setup มันค่อนข้างซับซ้อน เพราะเราต้องมานั่งเขียน Setting และจัดการ Provisioning Profile และ Service Account ใน iOS และ Android เดี๋ยวพี่มีเวลา พี่จะเขียนอีกบทความนึงเรื่องการ Setup แยกไปเลยให้อ่านนะ

วิธีการลง Fastlane Plugin: Versioning

ทุกโปรเจ็คของ iApp จะต้องใช้ Plugin ตัวนึงของ Fastlane ชื่อว่า “Versioning” มันจะขึ้นเลข Version ให้เราใน iOS และ Androidโดยอัตโนมัติ (เราไม่สามารถอัพแอพที่มีเลขเวอร์ชั่นเดิมหรือต่ำกว่า แอพที่อยู่บน Store ได้) และพี่ก็ขี้เกียจมานั่งอัพเดทเองทุกครั้งที่จะปล่อยขึ้น วิธีการลงก็คือ (ลงแค่ครั้งเดียวเท่านั้น ต่อ Proj)

$ cd /projName
$ fastlane add_plugin versioning

วิธีการให้ Fastlane เห็น Zipalign

แก้ไขไฟล์ .profile หรือ .bash_profile แล้วใส่คำสั่ง

export PATH=$PATH:{ANDROID_SDK}/build-tools/24.0.2

โดยที่ {ANDROID_SDK} คือ Path Android SDK ของเรา
เลข Version 24.0.2 เปลี่ยนเป็น Version ของเรา ที่เรามี เป็นอะไรก็ได้

วิธีการใช้ Fastlane

iOS

หากต้องการอัพโหลดแอพขึ้น TestFlight บน iOS พิมพ์ดังนี้

$ cd /projName
$ fastlane ios beta

ระบบจะ Build และขึ้น Version ให้และนำส่ง iTune Connect ให้เรา ทุกคนสามารถ Build ได้ตามใจต้องการเลย เพราะมันจะเอาขึ้นถึงแค่ระบบ Test Flight เท่านั้น ถ้าเราอยากให้ขึ้น App Store เลย เรายังคงต้องส่งมือใน iTune Connect อยู่ (Apple ไม่ยอมจ้า) เดี๋ยวพี่เป็นคนเอาขึ้น App Store ให้

Android

หากต้องการอัพโหลดแอพขึ้น TestFlight บน iOS พิมพ์ดังนี้

$ cd /projName
$ fastlane android beta

ระบบจะ Build และขึ้น Version ให้และนำส่ง Google Play Store ให้เรา ทุกคนสามารถ Build ได้ตามใจต้องการเลย เพราะมันจะเอาขึ้นถึงแค่ระบบ Beta Test เท่านั้น ถ้าเราอยากให้ขึ้น Production Play Store เลย เรายังคงต้องส่งมือใน Play Store Dev Console อยู่ดี แจ้งพี่ เดี๋ยวพี่เป็นคนเอาขึ้น Play Store ให้

ปัญหาที่พบบ่อย

Android ชอบส่ง app-debuk.apk แทนจะเป็น app-release-unsigned.apk

เวลาเรารัน fastlane android beta ระบบมันจะ Build apk เก็บไว้เป็นชื่อไฟล์ /android/app/build/outputs/apk/app-release-unsigned.apk เราต้องส่งไฟล์นี้ขึ้น Play Store

หากเราเคยรัน Android App ใน Android Studio, มันจะ ​Complie แล้วเก็บไฟล์ apk ไว้ที่ /android/app/build/outputs/apk/app-debug.apk

ปัญหาก็คือใน Fastlane มันจะเอาไฟล์แรกใน DIR /android/app/build/outputs/apk/ ไปส่ง Play Store โดยไม่สนชื่อหรืออะไรทั้งนั้น, เผอิญ app-debuk.apk มันมาก่อน app-release-unsigned.apk เราเลยต้องมั่นใจว่าไม่มีไฟล์ใดๆ ยกเว้นไฟล์ app-release-unsigned.apk อยู่ใน /android/app/build/outputs/apk/ นี้ หากมีไฟล์อื่นๆ มันจะส่งไม่ได้จ๊ะ

Setting ของ Fastlane

Appfile

/fastlane/Appfile คือไฟล์ข้อมูลจำเพาะของ App ใช้ในการส่ง

# iOS
app_identifier "com.learnfastlane"
apple_id "kobkit@gmail.com" # Your Apple email address
# Android
json_key_file "./fastlane/google-play-api-secret.json"
package_name "com.learnfastlane" # You Android app package

Fastfile

/fastlane/Fastfile คือไฟล์ Setting ในการส่ง Build ของ Fastlane อารมณ์เหมือน Gruntfile แต่เขียนด้วย Ruby

# Customise this file, documentation can be found here:
# https://github.com/fastlane/fastlane/tree/master/docs
# All available actions: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Actions.md
# can also be listed using the `fastlane actions` command
# Change the syntax highlighting to Ruby
# All lines starting with a # are ignored when running `fastlane`
# If you want to automatically update fastlane if a new version is available:
# update_fastlane
# This is the minimum version number required.
# Update this, if you use features of a newer version
fastlane_version "1.81.0"
default_platform :iosplatform :ios do
before_all do
ENV["SLACK_URL"] = "https://hooks.slack.com/services/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"
@slackChannel = "dev"
@username = "kobkrit@gmail.com"
@xcodeproj = "./ios/learnFastLane.xcodeproj"
@xcworkspace = "./ios/learnFastLane.xcworkspace"
@scheme = "learnFastLane"
@target = "learnFastLane"
end
desc "Tests Slack"
lane :test_slack do
# snapshot
slack(
channel: @slackChannel ,
message: "You can post to Slack!",
payload: { # Optional, lets you specify any number of your own Slack attachments.
'Build Date' => Time.new.to_s
},
default_payloads: [:git_branch, :git_author], # Optional, lets you specify a whitelist of default payloads to include. Pass an empty array to suppress all the default payloads. Don't add this key, or pass nil, if you want all the default payloads. The available default payloads are: `lane`, `test_result`, `git_branch`, `git_author`, `last_git_commit`.
)
end
desc "Runs all the tests"
lane :test do
scan
end
desc "Submit a new Beta Build to Apple TestFlight"
desc "This will also make sure the profile is up to date"
lane :beta do
# UI.important "Important Message"
# UI.error "O NO!"
# UI.message "Basic blah"
# answer = UI.select("Would you like to add a Changelog?", ["Yes", "No"])
# changelog = ask('Type Changelog message:') if answer == "Yes"
changelog = ""
# Do application testing here
# Very personal process
# always get latest certificates/provisioning profiles
match(type: 'appstore')
# Change build number
increment_build_number(
xcodeproj: @xcodeproj
)
increment_version_number_in_plist(
bump_type: 'minor',
xcodeproj: @xcodeproj,
target: @target
)
gym(
project: @xcodeproj,
scheme: @scheme
# hypothetically could be a workspace
# especially if you bring in pods!
# workspace: "./ios/AppName.xcworkspace"
)
# Sometimes itunes email is different!
if changelog
pilot(username: @username)
else
pilot(username: @username, changelog: changelog)
end
# Print reminder of testers
sh "pilot list -u #{@username}"
end# You can define as many lanes as you wantafter_all do |lane|
# This block is called, only if the executed lane was successful
slack(
channel: @slackChannel,
message: "App successfully released",
)
end
error do |lane, exception|
slack(
channel: @slackChannel,
message: exception.message,
success: false
)
end
end
platform :android do
before_all do
ENV["SLACK_URL"] = "https://hooks.slack.com/services/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"
@slackChannel = "dev"
end
desc "Tests Slack"
lane :test_slack do
# snapshot
slack(
channel: @slackChannel,
message: "You can post to Slack!",
payload: { # Optional, lets you specify any number of your own Slack attachments.
'Build Date' => Time.new.to_s
},
default_payloads: [:git_branch, :git_author], # Optional, lets you specify a whitelist of default payloads to include. Pass an empty array to suppress all the default payloads. Don't add this key, or pass nil, if you want all the default payloads. The available default payloads are: `lane`, `test_result`, `git_branch`, `git_author`, `last_git_commit`.
)
end
desc "Build"
lane :build do
# Get the last version code and increment it.
versionCode = File.read("metadata/versionCode").to_i
versionCode = versionCode+1
f = File.new("metadata/versionCode", "w")
f.write(versionCode)
f.close
# Build the release version of the Android App
gradle(
task: "assemble",
build_type: "Release",
project_dir: "android/",
properties: { 'VERSION_CODE' => versionCode }
)
sh "jarsigner -sigalg MD5withRSA -digestalg SHA1 -keystore ./android.jks -storepass kobkrit -signedjar ../android/app/build/outputs/apk/app-signed.apk ../android/app/build/outputs/apk/app-release-unsigned.apk androidkey"
sh "zipalign -f -v 4 ../android/app/build/outputs/apk/app-signed.apk ../android/app/build/outputs/apk/app-release-unsigned.apk"
sh "rm ../android/app/build/outputs/apk/app-signed.apk"
end
desc "Submit a new Beta Build to Google Play Store"
lane :upload do
# Uploads generated apk to Google Play Store as an Alpha build
supply(
track: "beta",
apk: "android/app/build/outputs/apk/app-release-unsigned.apk"
)
end
desc "Build and Submit a new Beta Build to Google Play Store"
lane :beta do
build()
upload()
end
# You can define as many lanes as you wantafter_all do |lane|
# This block is called, only if the executed lane was successful
slack(
channel: @slackChannel,
message: "App successfully released",
)
end
error do |lane, exception|
slack(
channel: @slackChannel,
message: exception.message,
success: false
)
end
end
# More information about multiple platforms in fastlane: https://github.com/fastlane/fastlane/blob/master/docs/Platforms.md
# All available actions: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Actions.md
# fastlane reports which actions are used
# No personal data is recorded. Learn more at https://github.com/fastlane/enhancer

ไฟล์นี้จะกำหนดงานที่ต้องทำในแต่ละ Lane โดยสรุปก็คือ Build, Up Version, Sign, Upload, และแจ้งผลการ Upload ที่ Slack ถึงผลการส่ง หากใครสนใจลองอ่าน Code ดูได้จ๊ะ

ในกรณีที่ใช้ Podfile ต้องเปลี่ยนจาก Proj.xcodeproj เป็น Proj.xcworkspace และ หัวจาก xcodeproj เป็น workspaceใน Fastfile ด้วยนะจ๊ะ

MatchFile

git_url "git@github.com:kobkrit/kobkritKey.git"
username "kobkrit@gmail.com"
app_identifier "com.learnfastlane"
type "development" # The default type, can be: appstore, adhoc or development# app_identifier ["tools.fastlane.app", "tools.fastlane.app2"]
# username "user@fastlane.tools" # Your Apple Developer Portal username
# For all available options run `match --help`
# Remove the # in the beginning of the line to enable the other options

จะบอกถึง GIT URL ในการได้มาถึง Provisioning Profile

/android/app/build.gradle

apply plugin: "com.android.application"import com.android.build.OutputFiledef VERSION_CODE=1
def VERSION_NAME="1."
if (project.hasProperty("VERSION_CODE")) {
VERSION_CODE=project.getProperty("VERSION_CODE")
}
if (project.hasProperty("VERSION_NAME")) {
VERSION_NAME=project.getProperty("VERSION_NAME")
}
/**
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
* and bundleReleaseJsAndAssets).
...
/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "com.learnfastlane"
minSdkVersion 16
targetSdkVersion 22
versionCode VERSION_CODE as Integer
versionName VERSION_NAME

ndk {
abiFilters "armeabi-v7a", "x86"
}
}
...

ทำให้เลข Version มันขึ้นเองได้

ios/learnFastLane/Info.plist

Add this as well.

<key>ITSAppUsesNonExemptEncryption</key><false/>

Example (ลองเล่น)

ลองเล่นได้เลยที่ https://github.com/kobkrit/learnFastLane

--

--