When developing an app you generally go through many cycles of testing your app on a physical device. That’s very easy with flutter. You just say one of these:
flutter build apk --release
flutter build apk --debug
flutter build apk --profile
possibly preceded by:
flutter clean
to clear any cached build artifacts. Then you simply run:
flutter install
There’s usually no need to script this. But sometimes, you just want to make life a bit easier. That’s why I use the following Bash script in all my Flutter apps.
It lets you choose between release, debug, or profile build types, and whether to do a clean install or an upgrade.
A clean install will first uninstall your app (erasing all data) before installing the new APK — which is sometimes exactly what you want. But often, I prefer to keep my app data intact and just update the app. That’s easily done using adb
(Android Debug Bridge) with the -r
flag.
Testing both flows is important, especially if you’ve made changes to your data structures.
At the end of the script, you can choose which connected device (including emulators) you want to install the app on.

Here it is — just save it as build-and-install-apk.sh
in the root of your project.
#!/bin/bash
# build-and-install-apk.sh
# This script performs the following:
# 1. Asks user what build type to create (release, debug, or profile)
# 2. Asks if user wants to install or upgrade the app
# 3. Asks which device to install the app on
# 4. Cleans the Flutter project using `flutter clean`
# 5. Builds APK for the selected build type
# 6. Installs or upgrades the app on the selected device
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
WHITE='\033[1;37m'
NC='\033[0m' # No Color
# Function to print colored output
print_color() {
echo -e "${1}${2}${NC}"
}
# Start the script
print_color $CYAN "Starting the Flutter build and install process..."
# Ask user for build type
print_color $YELLOW "Select build type to create:"
print_color $WHITE "1. Release (optimized, smaller size, no debugging info)"
print_color $WHITE "2. Debug (includes debugging symbols, larger size)"
print_color $WHITE "3. Profile (performance profiling build)"
read -p "Enter your choice (1-3): " build_choice
# Set build type and file path based on user selection
case $build_choice in
1)
build_type="release"
build_command="flutter build apk"
print_color $GREEN "You selected: Release build"
;;
2)
build_type="debug"
build_command="flutter build apk --debug"
print_color $GREEN "You selected: Debug build"
;;
3)
build_type="profile"
build_command="flutter build apk --profile"
print_color $GREEN "You selected: Profile build"
;;
*)
print_color $YELLOW "Invalid selection. Defaulting to release build."
build_type="release"
build_command="flutter build apk"
;;
esac
# Ask user if they want to install or upgrade
print_color $YELLOW "Select installation method:"
print_color $WHITE "1. Install (uninstalls first, clears all app data)"
print_color $WHITE "2. Upgrade (preserves app data)"
read -p "Enter your choice (1-2): " install_choice
# Set installation method based on user selection
case $install_choice in
1)
install_method="install"
print_color $GREEN "You selected: Install (fresh installation)"
;;
2)
install_method="upgrade"
print_color $GREEN "You selected: Upgrade (preserve data)"
;;
*)
print_color $YELLOW "Invalid selection. Defaulting to upgrade to preserve data."
install_method="upgrade"
;;
esac
# Ask user which device to use for installation
{
# Get list of connected devices
devices_output=$(adb devices 2>/dev/null)
if [ $? -ne 0 ]; then
print_color $RED "✗ Error: ADB not found or not accessible. Please ensure Android SDK is installed and in your PATH."
exit 1
fi
# Parse device lines (skip first line which is "List of devices attached")
device_lines=$(echo "$devices_output" | tail -n +2 | grep -v '^$')
if [ -z "$device_lines" ]; then
print_color $RED "✗ Error: No devices connected. Please connect a device and try again."
exit 1
fi
# Parse device IDs
device_ids=()
while IFS= read -r line; do
if [[ $line =~ ([^[:space:]]+)[[:space:]]+device ]]; then
device_ids+=("${BASH_REMATCH[1]}")
fi
done <<< "$device_lines"
# Handle case with multiple devices
selected_device_id=""
if [ ${#device_ids[@]} -gt 1 ]; then
print_color $YELLOW "Multiple devices detected:"
for i in "${!device_ids[@]}"; do
device_info=$(adb -s "${device_ids[$i]}" shell getprop ro.product.model 2>/dev/null | tr -d '\r')
print_color $WHITE " $((i+1)). ${device_ids[$i]} - $device_info"
done
read -p "Select a device (1-${#device_ids[@]}): " device_choice
device_index=$((device_choice - 1))
if [ $device_index -ge 0 ] && [ $device_index -lt ${#device_ids[@]} ]; then
selected_device_id="${device_ids[$device_index]}"
device_model_info=$(adb -s "$selected_device_id" shell getprop ro.product.model 2>/dev/null | tr -d '\r')
print_color $GREEN "Selected device: $selected_device_id - $device_model_info"
else
print_color $YELLOW "Invalid device selection. Using first device: ${device_ids[0]}"
selected_device_id="${device_ids[0]}"
device_model_info=$(adb -s "$selected_device_id" shell getprop ro.product.model 2>/dev/null | tr -d '\r')
fi
else
selected_device_id="${device_ids[0]}"
device_model_info=$(adb -s "$selected_device_id" shell getprop ro.product.model 2>/dev/null | tr -d '\r')
print_color $GREEN "Using connected device: $selected_device_id - $device_model_info"
fi
} || {
print_color $RED "✗ Error: Failed to detect connected devices."
exit 1
}
# Set APK file path (handle Windows/Unix path differences)
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
# Windows (Git Bash)
apk_file="$(pwd)/build/app/outputs/flutter-apk/app-$build_type.apk"
else
# macOS/Linux
apk_file="$(pwd)/build/app/outputs/flutter-apk/app-$build_type.apk"
fi
# Step 1: Run `flutter clean`
print_color $YELLOW "Step 1/3: Running 'flutter clean' to clean the project..."
if flutter clean; then
print_color $GREEN "✓ 'flutter clean' completed successfully."
else
print_color $RED "✗ Error: Failed to run 'flutter clean'. Ensure Flutter is installed and in your PATH."
exit 1
fi
# Step 2: Build the APK with selected build type
print_color $YELLOW "Step 2/3: Running '$build_command'..."
if eval "$build_command"; then
print_color $GREEN "✓ '$build_command' completed successfully."
# Get APK info
if [ -f "$apk_file" ]; then
file_size=$(stat -f%z "$apk_file" 2>/dev/null || stat -c%s "$apk_file" 2>/dev/null)
if [ -n "$file_size" ]; then
size_mb=$(echo "scale=2; $file_size / 1048576" | bc 2>/dev/null || echo "scale=2; $file_size / 1048576" | awk '{print $1/1048576}')
print_color $CYAN " APK built: $apk_file"
print_color $CYAN " Size: ${size_mb} MB"
else
print_color $CYAN " APK built: $apk_file"
fi
else
print_color $YELLOW " Note: Expected APK not found at $apk_file"
print_color $YELLOW " Check the build/app/outputs/flutter-apk directory for your APK"
fi
else
print_color $RED "✗ Error: Failed to build APK. Check for build errors."
exit 1
fi
# Step 3: Install/upgrade on the previously selected device
print_color $YELLOW "Step 3/3: Deploying the app to selected device ($selected_device_id - $device_model_info)..."
{
# Find the APK path for the current build type
full_apk_path=""
if [ -f "$apk_file" ]; then
full_apk_path="$apk_file"
else
print_color $YELLOW " Warning: Could not find the APK file. Looking for alternatives..."
# Find any APK in the flutter-apk directory
possible_apk=$(find "build/app/outputs/flutter-apk" -name "*.apk" -type f 2>/dev/null | head -n 1)
if [ -n "$possible_apk" ]; then
full_apk_path="$possible_apk"
print_color $CYAN " Found alternative APK: $full_apk_path"
else
print_color $RED "✗ Error: No APK files found. Build may have failed."
exit 1
fi
fi
# Install or upgrade the app based on user choice
if [ "$install_method" = "install" ]; then
# Use flutter install with the appropriate build type and device
install_command="flutter -d $selected_device_id install"
if [ "$build_type" = "debug" ]; then
install_command="$install_command --debug"
elif [ "$build_type" = "profile" ]; then
install_command="$install_command --profile"
fi
print_color $CYAN " Installing app on device $selected_device_id..."
if eval "$install_command"; then
print_color $GREEN "✓ App ($build_type build) installed successfully on device."
else
print_color $RED "✗ Error: Failed to install app using flutter install."
exit 1
fi
else
# Use ADB to install with the -r flag (upgrade) targeting specific device
print_color $CYAN " Upgrading app using adb (preserving data)..."
upgrade_command="adb -s $selected_device_id install -r \"$full_apk_path\""
upgrade_result=$(eval "$upgrade_command" 2>&1)
if echo "$upgrade_result" | grep -q "Success"; then
print_color $GREEN "✓ App ($build_type build) upgraded successfully on device $selected_device_id."
else
print_color $YELLOW "✗ Warning: There might be an issue with the upgrade: $upgrade_result"
print_color $YELLOW " Attempting fallback to flutter install..."
# Fallback to flutter install with specific device
install_command="flutter -d $selected_device_id install"
if [ "$build_type" = "debug" ]; then
install_command="$install_command --debug"
elif [ "$build_type" = "profile" ]; then
install_command="$install_command --profile"
fi
if eval "$install_command"; then
print_color $YELLOW " Note: Fallback installation successful, but app data may have been lost."
else
print_color $RED "✗ Error: Both upgrade and fallback installation failed."
exit 1
fi
fi
fi
} || {
print_color $RED "✗ Error: Failed to deploy the app."
exit 1
}
# End script
print_color $CYAN "✓ Flutter build and deploy process completed successfully!"
if [ "$install_method" = "upgrade" ]; then
print_color $GREEN "The $build_type build of your app has been upgraded on device $selected_device_id, preserving app data."
else
print_color $GREEN "The $build_type build of your app has been freshly installed on device $selected_device_id."
fi
Bash