CI CD with Fastlane - tungpham6195/flutter GitHub Wiki

Welcome to CI-CD with Fastlane

Refer Fastlane documents

Setup environment for Fastlane

⛔️ WARNING: TRY NOT TO INSTALL GEM WITH SUDO ⛔️

  • Install Bundle: gem install bundler

  • Install Fastlane by using Brew: brew install fastlane

  • Install Fastlane by using Gem: gem install fastlane

  • Xcode command line tools: xcode-select --install

  • Install Dotenv: gem install dotenv

  • Make sure you local is using UTF-8 by updating .bash_profile or .zshrc

    export LC_ALL=en_US.UTF
    export LANG=en_US.UTF-8
  • Setup CredentialsManager for uploading to AppStore: Refer this guide

    • Run fastlane fastlane-credentials add --username <your_email>, then following the guide
  • Ignore these files:

#fastlane
ios/fastlane/report.xml
ios/fastlane/README.md
android/fastlane/report.xml
android/fastlane/README.md

Initialize Fastlane for Flutter project

Initialize for Android

  • Go to android/ folder: cd android/

  • then: fastlane init and following the instruction

  • Setup Supply for uploading .aab file to Google Play Store: Refer this guide

    • Rename that json file you have just downloaded to play-store-console-service-key.json

    • Store it at $HOME/deployment/anth/play-store-console-service-key.json

      • Test the connection to Google Play Store after setting up Supply:
      fastlane run validate_play_store_json_key json_key:$HOME/deployment/anth/play-store-console-service-key.json
      • Update your Appfile:
      json_key_file("../../../../deployment/anth/play-store-console-service-key.json") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
      package_name("com.thunhung.android") # e.g. com.krausefx.app
      • Run fastlane supply init to dowload the metadata from Google Play Console. e.g:

        fastlane_android_metadata

Initialize for IOS

  • Go to ios/ folder: cd ios/

  • Then: fastlane init and following the instruction

  • Add Cocoapods: add this line gem "cocoapods" to ios/Gemfile

  • Then run sudo bundle install to update Gemfile

Authenticating with Apple services

We use this method to connect to App Store Connect API which is used for uploading ios app

Recommend using Method 1: App Store Connect API key for this setup

  • Setup App Store Connect API: Refer this guide
    1. Go to App Store Connect API: fastlane_app_store_api_key

    2. Create new .p8 key:

      NOTE: select the role which is possible to be used for uploading app, e.g: Admin, Developer, App Manager

      fastlane_app_store_api_key_create_new

    3. After creating, down load .p8 file and store it in $HOME/deployment/anth/AuthKey_1236GWG52Z.p8

      NOTE: This file can be dowloaded 1 time only after creating, please keep it safety

    4. Open .p8 with text editor then copy the content of its to other editor place

      fastlane_app_store_connect_p8_view_content

    5. Repalce all break-line with \n

    6. Create App Store API key as json file instead of using .p8: Refer this guide

      File name: $HOME/deployment/anth/app-store-connect-service-key.json

      {
        "key_id": "D403SF739",
        "issuer_id": "7063b7fe-68a8-4acb-89be-165aa6465141",
        "key": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwESJDFKHSKJFHKSJDHHBHkwdwIBAQQghVhY1reB3nYpJRnw\neY0YHVfr/e11RO42iFJ3LMYEApKgCgYIKoZIzj0DAQehRANCAARZnLGGRA1nC0yi\nN3ym4ZRrUYp6/89/AS7G18fwtYFipFlXEkqif17vecGeVp6+9YfxAArADpNtz3KL\n1a7td4Nc\n-----END PRIVATE KEY-----",
        "duration": 1200
      }

      Where you can get key_id and issuer_id:

      fastlane_app_store_api_key_issuer_and_key_id

Setup Deliver for uploading .ipa file to App Store Connect: Refer this guide

  1. Update your Appfile:
app_identifier "com.thunhung.ios" # The bundle identifier of your app
apple_id "[email protected]" # Your Apple email address
itc_team_name "Huynh Thai An"
itc_team_id "232069864"
  1. Run fastlane deliver init --use_live_version true to dowload the metadata from App Store Connect. e.g:

fastlane_ios_metadata

Install firebase_app_distribution plugin

Refer Firebase Distribution

  • run fastlane add_plugin firebase_app_distribution in /android and /ios
  • then run sudo bundle install in /android and /ios
  • following this instruction for completing configuration for Android and IOS
  • create service account in Google Cloud Console: Refer this guide
    • store account service json file at: $HOME/deployment/anth/kaylee-d2522-8adc06fcdda4.json
    • you need to put GOOGLE_APPLICATION_CREDENTIALS into your .bash_profile or .zshrc: export GOOGLE_APPLICATION_CREDENTIALS ="$HOME/deployment/anth/kaylee-d2522-8adc06fcdda4.json"
  • create text file for storing metadata for uploading firebase distribution
    • groups.txt:

      client-tester,internal_tester
      
    • testers.txt:

    • release-notes.txt:

      this is your release note:
      - update the feature
      
    • Those files will be looked like this:
      fastlane-firebase-distribution-metadata

Add environment variables

Refer dotenv

  1. Create general configuration in $root/.env(.env file)
#Configuration for Firebase Distribution
FIREBASEAPPDISTRO_TESTERS_FILE=../testers.txt
FIREBASEAPPDISTRO_GROUPS_FILE=../groups.txt
FIREBASEAPPDISTRO_RELEASE_NOTES_FILE=../release-notes.txt
	
#Configuration for building IPA
GYM_OUTPUT_DIRECTORY=../build
GYM_CLEAN=true
GYM_XCARGS=-allowProvisioningUpdates

#split-debug-info for dart obfuscate
SPLIT_DEBUG_INFOPATH=build
	
CONTENT_IMAGE=../android/fastlane/metadata/android/vi/images/icon.png
  1. Create configuration for each flavors $root/.env.<your_flavor> (.env.<your_flavor> file)

    fastlane-dotenv-files

  • E.g: if you have 2 flavors prod and dev => you need to create 2 env files => .env.dev and .env.prod
  • We often have 2 kinds of environment: production and development
  • Configuration for development: so everythings in this configuration will be used for deploying for internal testing
#Configuration for Firebase Distribution
#for Android
FIREBASE_ANDROID_APP_ID=1:165119837573:android:684877c544aa943455f0ec
FIREBASEAPPDISTRO_APK_PATH=../build/app/outputs/flutter-apk/app-dev-release.apk
#for IOS
FIREBASE_IOS_APP_ID=1:165119837573:ios:eb924c661bdcc60255f0ec
FIREBASEAPPDISTRO_IPA_PATH=../build/Runner.ipa
	
#flutter build config
TARGET=lib/main_dev.dart
	
APP_NAME=[Dev] Thu Nhung
  • Configuration for production: this configuation will be used for uploading to app store
#Firebase config
#for Android
FIREBASE_ANDROID_APP_ID=1:165119837573:android:c496c80ec9ddfbbf55f0ec
#for IOS
FIREBASE_IOS_APP_ID=1:165119837573:ios:e70715c0bdadcf8a55f0ec
	
#App Store Connect
DELIVER_FORCE=true
DELIVER_AUTOMATIC_RELEASE=false
DELIVER_RUN_PRECHECK_BEFORE_SUBMIT=false
DELIVER_IPA_PATH=../build/Runner.ipa
DELIVER_API_KEY_PATH=../../../../deployment/anth/app-store-connect-service-key.json
DELIVER_OVERWRITE_SCREENSHOTS=true
	
#Google Play Store
SUPPLY_AAB=../build/app/outputs/bundle/prodRelease/app-prod-release.aab
SUPPLY_RELEASE_STATUS=draft
	
#flutter build config
TARGET=lib/main.dart
	
APP_NAME=Thu Nhung

Write Fastfile

  1. Create GeneralFastFile in $root/GeneralFastFile:
import('../../CommonFastfile')
import('../../NotificationFastfile')

private_lane :load_env do
    Dir.chdir('../..') do
        Dotenv.load('.env')
        Dotenv.load(".env.#{ENV['FASTLANE_LANE_NAME']}")
    end
end

lane :clean_project do
    Dir.chdir('../..') do
        sh('flutter', 'clean')
        sh('flutter', 'packages', 'get')
    end
end

lane :flutter_build do
    load_env
    build
end

private_lane :build do
    Dir.chdir('../..') do
        command = ['flutter', 'build', build_type, '--flavor', flavor , '--target', ENV['TARGET'], '--obfuscate', '--split-debug-info', "#{ENV['SPLIT_DEBUG_INFOPATH']}"]
        sh(
            command: command
        )
    end
end

private_lane :build_type do
    platform_name == 'ios'? platform_name : flavor == 'prod'? 'appbundle' : 'apk'
end

lane :build_ios_and_export_ipa do
    ENV['GYM_SCHEME'] = flavor
    ENV['GYM_EXPORT_METHOD'] = ENV['GYM_SCHEME'] == 'prod' ? 'app-store' : 'development'
    gym
end

lane :uploading_firebase_distribution do
    if platform_name == 'ios' then
        ENV['FIREBASEAPPDISTRO_APP'] = ENV['FIREBASE_IOS_APP_ID']
        ENV['FIREBASEAPPDISTRO_APK_PATH'] = ''
    elsif platform_name == 'android' then
        ENV['FIREBASEAPPDISTRO_APP'] = ENV['FIREBASE_ANDROID_APP_ID']
        ENV['FIREBASEAPPDISTRO_IPA_PATH'] = ''
    end

    firebase_app_distribution(
        service_credentials_file: ENV['GOOGLE_APPLICATION_CREDENTIALS']
    )
    firebase_distribution_notification
end

lane :upload_store do
    if platform_name == 'ios' then
        deliver
    elsif platform_name == 'android' then
        supply
    end

    upload_store_notification
end

lane :update_gem do
    sh(command: ['bundle', 'update'])
end
  1. Create CommonFastfile in $root/CommonFastfile:
lane :platform_name do
	ENV['FASTLANE_PLATFORM_NAME']
end

lane :flavor do
	ENV['FASTLANE_LANE_NAME']
end
  1. Create NotificationFastfile in $root/NotificationFastfile:
private_lane :push_notification do |options|
    sh('pwd')
    notification(
        title: 'Fastlane',
        subtitle: options[:subtitle],
        message: options[:message],
        content_image: ENV['CONTENT_IMAGE'],
    )
end

lane :firebase_distribution_notification do
    push_notification(
        subtitle: 'Firebase Distribution',
        message: "Successfully distributed #{ENV['APP_NAME']} #{platform_name}"
    )
end

lane :upload_store_notification do
    push_notification(
        subtitle: platform_name=='ios'? 'App Store Connect': 'Google Play Store',
        message: "Successfully uploaded #{ENV['APP_NAME']}"
    )
end
  1. Update Fastfile action for Android and IOS

    NOTE: your lane name should be the same as your flavor, e.g: dev, prod

    • Fastfile of Android:
    require 'dotenv'
    import('../../GeneralFastfile')
    
    default_platform(:android)
    platform :android do
        desc "build apk/aab"
    
        before_all do
            update_gem
            flutter_build
        end
        lane :dev do
            uploading_firebase_distribution
        end
    
        lane :prod do
            upload_store
        end
    end
    • Fastfile of IOS:
    require 'dotenv'
    import('../../GeneralFastfile')
    
    default_platform(:ios)
    platform :ios do
        desc "Build an ipa"
    
        before_all do
            update_gem
            clean_project
            flutter_build
            build_ios_and_export_ipa
        end
    
        lane :dev do
            uploading_firebase_distribution
        end
    
        lane :prod do
            upload_store
        end
    end

Write script

  • create script file with name deploy.sh and put it into <your_project>/deploy.sh

  • script content:

     red='\e[31m'
     green='\e[32m'
     yellow='\e[33m'
     white='\e[97m'
     reset='\e[0m'
     bold='\e[1m'
     greenBg='\e[42m'
     yellowBg='\e[43m'
     magentaBg='\e[45m'
     
     you_re_here="You're here: "
     
     flavor='dev'
     
     run_fastlane() {
         bundle exec fastlane $1
     }
     
     select_flavor() {
         echo "Welcome to the show\n"
         echo -e "${red}${bold}[1]${reset}${green}: [dev] Development${reset}"
         echo "${yellow}${bold}[2]${reset}${green}: [prod] Production${reset}"
         printf "${green}\nPlease select your flavor, default is ${magentaBg}${white}[dev]${reset} (Press ${white}${yellowBg}enter${reset} to use the default): ${reset}"
         read value
     
         if [ "$value" = "2" ]
         then
           flavor="prod"
         fi
     }
     
     deploy() {
         select_flavor
         cd ios
         echo "${you_re_here}${white}${bold}${greenBg}$(pwd)${reset}"
         run_fastlane $flavor
         cd ../android
         echo "${you_re_here}${white}${bold}${greenBg}$(pwd)${reset}"
         run_fastlane $flavor
     }
     
     deploy
⚠️ **GitHub.com Fallback** ⚠️