Reputation: 906
digging into fastlane for the first time on a react native project, and in the process of getting a demo version up for internal testing on both the google play store, and testflight.
After following some tutorials for getting the android bundle up successfully, I ended up with some lanes that do increment both the versionCode
and versionName
, then bundle and push to the google play store via the supply command.
Now I'm moving onto some iOS lanes, and I'm wondering the best way to sync these up. Ideally, I'd like to use the package.json version
as the one source of truth for this, but I'm lost a little on the best way to tie it all in, so I'm asking for some advice/workflows that others have found successful with fastlane and building/versioning their ios/android apps. Thanks!
Here are my current android lanes.
desc "Build a version of the app, that allows params for task and type"
lane :build do |options|
build_task = options.fetch(:build_task, "bundle")
build_type = options.fetch(:build_type, "Release")
gradle(task: "clean")
gradle_params = {
task: build_task,
build_type: build_type,
}
gradle(gradle_params)
end
##### ---------------------------------------------
desc "Build and push a new internal build to the Play Store"
lane :internal do
build()
supply_params = {
track: "internal",
release_status: "draft",
}
supply(supply_params)
end
##### ---------------------------------------------
desc "Increment build number and push to repository"
lane :inc_build_number do |options|
params = {
:app_project_dir => 'app'
}
# Specify a custom build number to be passed in
if options[:build_number]
params[:version_code] = options[:build_number].to_i
end
android_increment_version_code(params)
new_version_code = Actions.lane_context[Actions::SharedValues::ANDROID_VERSION_CODE]
UI.important("Incremented android version code to #{new_version_code}")
end
##### ---------------------------------------------
desc "Increment version number and push to repository"
lane :inc_version_number do |options|
should_commit = options.fetch(:should_commit, true)
commit_message = options.fetch(:commit_message, "android: bump version code & number[skip ci]")
should_push = options.fetch(:should_push, true)
ensure_git_status_clean if should_commit
# increment build number first
inc_build_number
increment_type = options.fetch(:increment_type, "patch")
new_version_params = options[:version]
params = {
app_project_dir: 'app',
increment_type: increment_type,
}
unless new_version_params.nil?()
params[:version_name] = new_version_params
end
android_increment_version_name(params)
new_version_name = Actions.lane_context[Actions::SharedValues::ANDROID_VERSION_NAME]
UI.important("Incremented android version name to #{new_version_name}")
if should_commit
path = "android/app/build.gradle"
git_add(path: path)
git_commit(path: path, message: commit_message)
push_to_git_remote if should_push
end
end
Upvotes: 3
Views: 14272
Reputation: 3020
I think you should not use a single source for Android and iOS while versioning. Because versioning presents how your app changes in time.
Anyway, creating a good Fastlane configuration not an easy job, so I decide to share what I have got.
For Android, I created a file called version.properties
, I increment version number in this file before submitting a new version to Google Play Store and a Gradle script automatically generates build number in build time. I'm just increment version number (following Semver) and the script handles the rest. When I use Fastlane I'm doing same thing, Fastlane ask me new version, I give a version number to it then it changes the version.properties file all after then it compiles the app.
Follow Below Steps:
version.properties
under android/version
folderVERSION=7.0.5
versioning.gradle
under android
folder and write below code in this file:ext {
buildVersionCode = {
def versionName = buildVersionName()
def (major, minor, patch) = versionName.toLowerCase().tokenize('.')
(major, minor, patch) = [major, minor, patch].collect { it.toInteger() }
(major * 10000) + (minor * 100) + patch
}
buildVersionName = {
def props = new Properties()
file("../version/version.properties").withInputStream { props.load(it) }
return props.getProperty("VERSION")
}
}
android/app/build.gradle
file// ...
apply from: "../../node_modules/react-native/react.gradle"
apply from: '../versioning.gradle' // <- add this line
// ...
// ...
// ...
android {
// ...
defaultConfig {
// ...
versionCode buildVersionCode() // <- add this line
versionName buildVersionName() // <- add this line
// ...
}
// ...
}
// ...
You can change the version from android/version/version.properties
file now. Version code will automatically create while building. For an example, if you typed version like 7.3.5
your build number will be 70305
.
Now let's make Fastlane integration,
Install property_file_read
plugin to ability read property files: fastlane add_plugin property_file_read
You can use below Fastlane configuration for Android:
# CONSTANTS
NOTIFICATION_TITLE = "FOO APP FINISHED!"
platform :android do
desc "Choose release name"
private_lane :determine_release_name do |options|
versions = google_play_track_release_names(track: options[:track])
if versions.empty?
UI.user_error!("Whoops, current version not found!")
else
current_version = versions[0]
end
parts = current_version.split(".")
major = parts[0]
minor = parts[1]
patch = parts[2]
target_major = (major.to_i + 1).to_s + ".0.0"
target_minor = major + "." + (minor.to_i + 1).to_s + ".0"
target_patch = major + "." + minor + "." + (patch.to_i + 1).to_s
properties = property_file_read(file: "android/version/version.properties")
file_version = properties["VERSION"]
target_version_label = UI.select("What version do you want to use?", [
"Bump patch (#{target_patch})",
"Bump minor (#{target_minor})",
"Bump major (#{target_major})",
"KEEP EXISTING (#{file_version})",
"CUSTOM",
])
next target_major if target_version_label.match(/major/)
next target_minor if target_version_label.match(/minor/)
next target_patch if target_version_label.match(/patch/)
next file_version if target_version_label.match(/FILE/)
custom_version = prompt(text: "\nEnter New Version Number:")
custom_version
end
desc "Build and Deploy to Google Play Internal App Sharing"
lane :beta do
newVersion = determine_release_name(track: "internal")
# update version
File.open("../android/version/version.properties", "w") do |file|
file.write("VERSION=#{newVersion}")
end
# gradle(task: "clean", project_dir: "./android/")
gradle(task: "bundle", build_type: "Release", project_dir: "android")
upload_to_play_store(track: "internal", aab: "android/app/build/outputs/bundle/release/app-release.aab")
notification(title: NOTIFICATION_TITLE, subtitle: "Google Play - Internal App Sharing", message: "Finished!!")
end
end
I think it is the best system you can find for Fastlane integration on Android, I use these settings myself.
In iOS, I'm asking new version number then I compile and send app to the TestFlight, there is nothing special. You can check my iOS configuration below:
# CONSTANTS
NOTIFICATION_TITLE = "FOO APP FINISHED!"
IOS_XCWORKSPACE = "ios/Foo.xcworkspace"
IOS_XCODEPROJ = "ios/Foo.xcodeproj"
IOS_SCHEME = "Foo"
IOS_TARGET = "Foo"
APP_STORE_KEYFILE = "fastlane/app-store-auth-key.p8"
APP_STORE_KEY_ID = "xxxxxxxxxx"
APP_STORE_ISSUER_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
platform :ios do
desc "Choose release name"
private_lane :determine_release_name do
current_build_number = latest_testflight_build_number(api_key: lane_context[SharedValues::APP_STORE_CONNECT_API_KEY])
current_version = lane_context[SharedValues::LATEST_TESTFLIGHT_VERSION]
parts = current_version.split(".")
major = parts[0]
minor = parts[1]
patch = parts[2]
target_major = (major.to_i + 1).to_s + ".0.0"
target_minor = major + "." + (minor.to_i + 1).to_s + ".0"
target_patch = major + "." + minor + "." + (patch.to_i + 1).to_s
selected_version_ok = false
while selected_version_ok === false
target_version_label = UI.select("What version do you want to use?", [
"KEEP EXISTING (#{current_version})",
"Bump patch (#{target_patch})",
"Bump minor (#{target_minor})",
"Bump major (#{target_major})",
"CUSTOM",
])
if target_version_label.match(/CUSTOM/)
custom_version = prompt(text: "\nEnter New Version Number:")
if custom_version < current_version
UI.important "Wahaha, version (#{custom_version}) can't lower than the current version (#{current_version})"
else
selected_version_ok = true
end
else
selected_version_ok = true
end
end
next { version: target_major, type: "major" } if target_version_label.match(/major/)
next { version: target_minor, type: "minor" } if target_version_label.match(/minor/)
next { version: target_patch, type: "patch" } if target_version_label.match(/patch/)
next { version: current_version, type: "current" } if target_version_label.match(/KEEP/)
{ version: custom_version, type: "custom" }
end
desc "Push a new beta build to TestFlight"
lane :beta do
api_key = app_store_connect_api_key(
key_id: APP_STORE_KEY_ID,
issuer_id: APP_STORE_ISSUER_ID,
key_filepath: APP_STORE_KEYFILE,
duration: 1200, # optional (maximum 1200)
in_house: false, # optional but may be required if using match/sigh
)
new_version = determine_release_name
if new_version[:type] === "current"
# get latest build number from App Store
build_num = app_store_build_number(
initial_build_number: 1,
live: false,
version: get_version_number(xcodeproj: IOS_XCODEPROJ, target: IOS_TARGET),
api_key: api_key,
)
increment_build_number(build_number: build_num + 1, xcodeproj: IOS_XCODEPROJ)
else
increment_version_number(version_number: new_version[:version], xcodeproj: IOS_XCODEPROJ)
end
# 🏗️ Build app, this method has other options few we will explore in next section
build_app(
silent: true,
workspace: IOS_XCWORKSPACE,
scheme: IOS_SCHEME,
)
# ⏫ Its time to upload
upload_to_testflight(api_key: api_key, skip_waiting_for_build_processing: true)
# 🧽 Clear artifacts
clean_build_artifacts
sh "rm -rf \"#{lane_context[SharedValues::XCODEBUILD_ARCHIVE]}\""
notification(title: NOTIFICATION_TITLE, subtitle: "Testflight", message: "Finished!!")
end
end
Let me what you think in comments! (Btw sorry for my poor England :p)
Upvotes: 5