```sh # Name: Flutter Release Builds # Description: Generate release builds for flutter app # Author: Rohan Joshi # Twitter: @rohanjsh #!/bin/bash #! DEFAULTS #! Change these to your own #! PASS --use-defaults FLAG, TO USE THESE DEFAULTS default_num_flavors=2 default_flavors=("dev" "lib/main_dev.dart" "prod" "lib/main_prod.dart") default_build_types=("apk" "appbundle" "ipa") # DESCRIPTION # Automates Flutter project builds with flavor and type options. # Prerequisites: # - Flutter installed and configured. # - Bash shell environment. # Options: # --use-defaults: Use default values. # --no-flavor: Build without flavors. # Usage: # - Default: ./build_script.sh # - Custom: ./build_script.sh --use-defaults # - No Flavor: ./build_script.sh --no-flavor # How To Guide: # - Run the script and follow prompts for custom builds. # - Number of flavors # - Flavor name and entry point for each flavor # - Build types to generate # - Script creates timestamped build directories. # Exit on any error, unbound variable, or error in a pipeline set -euo pipefail #! +--------------------------+ #! | GLOBAL VARIABLES | #! +--------------------------+ # Define variables to store states of both flags use_defaults=false no_flavor=false # Define available flags valid_flags="use-defaults,h,no-flavor" # Get the current date and time current_date_time=$(date "+%Y-%m-%d_%H-%M-%S") #+---------------------------------+ #| Function Declarations | #+---------------------------------+ #! Function to run flutter commands, ALTER COMMANDS AS NEEDED (project specific) run_flutter_commands() { echo "🚀 Cleaning, fetching dependencies, and running build processes..." flutter clean flutter pub get dart pub get #! Uncomment the following line if you are using build_runner # dart run build_runner build --delete-conflicting-outputs } # Function to display an interactive menu for build types select_build_types() { if [[ $use_defaults == true ]]; then build_types=("${default_build_types[@]}") return fi echo "️ Select the build type(s) you want to generate:" echo " 1. APK" echo " 2. AAB (App Bundle)" echo " 3. IPA" read -p "Enter your choice (comma-separated numbers): " build_type_choice # Split the user input into an array of individual choices IFS=',' read -r -a build_type_choices <<<"$build_type_choice" build_types=() # Loop through each choice and validate it for choice in "${build_type_choices[@]}"; do case "$choice" in 1) build_types+=("apk") ;; 2) build_types+=("appbundle") ;; 3) build_types+=("ipa") ;; *) echo "Invalid choice: '$choice'. Ignoring." ;; esac done # Check if any valid choices were selected if [[ ${#build_types[@]} -eq 0 ]]; then echo "No valid build types selected. Exiting." && exit 1 fi } # Function to build and copy files build_and_copy_files() { if [ "$1" == "apk" ]; then flutter build $1 --flavor $2 -t $3 --split-per-abi else flutter build $1 --flavor $2 -t $3 fi # Create a directory for the build mkdir -p builds/$current_date_time/[$2-$1] cp -r $4* builds/$current_date_time/[$2-$1] echo "✅ Build completed for $2 flavor and type $1" } #function to call if no flavor is selected no_flavor_build(){ select_build_types run_flutter_commands # Directly run commands for each build type for build_type in "${build_types[@]}"; do case "$build_type" in "apk") flutter build apk --split-per-abi # Create a directory for the build mkdir -p builds/$current_date_time/[no-flavor-$build_type] # Copy files for each build type cp -r build/app/outputs/flutter-apk/* builds/$current_date_time/[no-flavor-$build_type] ;; "appbundle") flutter build appbundle # Create a directory for the build mkdir -p builds/$current_date_time/[no-flavor-$build_type] # Copy files over cp -r build/app/outputs/bundle/* builds/$current_date_time/[no-flavor-$build_type] ;; "ipa") flutter build ipa # Create a directory for the build mkdir -p builds/$current_date_time/[no-flavor-$build_type] # Copy files over cp -r build/ios/ipa/* builds/$current_date_time/[no-flavor-$build_type] ;; esac # Create a directory for the build mkdir -p builds/$current_date_time/[no-flavor-$build_type] # Copy files for each build type case "$build_type" in "apk") cp -r build/app/outputs/flutter-apk/* builds/$current_date_time/[no-flavor-$build_type] ;; "appbundle") cp -r build/app/outputs/bundle/* builds/$current_date_time/[no-flavor-$build_type] ;; "ipa") cp -r build/ios/ipa/* builds/$current_date_time/[no-flavor-$build_type] ;; esac done echo "🎉 All builds completed successfully!, you can find all the builds in /builds folder" open builds/$current_date_time exit 0 } #+-------------------------+ #| Process flags | #+-------------------------+ # Process options using getopts while getopts ":h-:" opt; do case $opt in -) case $OPTARG in use-defaults) use_defaults=true ;; h) echo "Usage: $0 [--$valid_flags]" exit 0 ;; no-flavor) no_flavor=true ;; *) echo "Invalid option: --$OPTARG" >&2 echo "Available options: $valid_flags" >&2 exit 1 ;; esac ;; h) echo "Usage: $0 [--$valid_flags]" exit 0 ;; \?) echo "Invalid option: -$OPTARG" >&2 echo "Available options: $valid_flags" >&2 exit 1 ;; esac done # Check if the no-flavor flag is set if [[ $no_flavor == true ]]; then no_flavor_build fi #+-------------------------+ #| Get arguments | #+-------------------------+ # Check if the flag is set and take appropriate action if [[ $use_defaults == true ]]; then num_flavors=$default_num_flavors flavors=("${default_flavors[@]}") select_build_types else # if no-flavor, then skip this step if [[ $no_flavor == false ]]; then read -p "🤔 Number of flavors? " num_flavors #if flavor is 0, then call if [[ $num_flavors -eq 0 ]]; then no_flavor_build fi # Array to store flavor information declare -a flavors # Prompt the user for flavor information for ((i = 1; i <= num_flavors; i++)); do read -p "🍦 Flavor $i name: " flavor_name read -p "📄 Entry point location of flavor $i (eg. lib/main_dev.dart): " main_dart_location #check if the file exists #if it does not exist, then ask again while [[ ! -f $main_dart_location ]]; do echo "🚫 File $main_dart_location does not exist, try again" read -p "📄 Entry point location of flavor $i (eg. lib/main_dev.dart): " main_dart_location done # Add flavor information to the array flavors+=("$flavor_name" "$main_dart_location") done select_build_types fi fi run_flutter_commands # Build and copy files for each flavor for ((i = 0; i < ${#flavors[@]}; i += 2)); do flavor_name=${flavors[i]} main_dart_location=${flavors[i + 1]} # Build and copy files for each build type for build_type in "${build_types[@]}"; do case "$build_type" in "apk") output_directory="build/app/outputs/flutter-apk/" ;; "appbundle") output_directory="build/app/outputs/bundle/" ;; "ipa") output_directory="build/ios/ipa/" ;; esac # Build and copy files for the flavor and build type build_and_copy_files $build_type $flavor_name $main_dart_location $output_directory done done open builds/$current_date_time echo "🎉 All builds completed successfully!, you can find all the builds in /builds folder" ```