Script to generate release builds for a Flutter Mobile App (Flavors Supported)

# 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"