Automate Your Ubuntu Flutter Setup
This guide provides an interactive way to understand and use a setup script that replicates the "batteries-included" experience of cloud IDEs like Project IDX on a local Ubuntu 24.04 machine. It automates the installation of a complete Flutter, Android, and Node.js toolchain, eliminating setup friction and ensuring consistency.
Flutter SDK
Installed via Git Clone from the official stable branch for precise version control.
Android SDK
Installed dynamically using sdkmanager with the latest versions found from the official repository.
Node.js
Managed with NVM (Node Version Manager) to easily switch between versions.
OpenJDK
Installed via APT using a stable, long-term support (LTS) version.
The Unified Setup Script
This is the core of the setup process. Save it as `setup_dev_env.sh`, make it executable with `chmod +x setup_dev_env.sh`, and run it with `./setup_dev_env.sh`. It will handle everything from installing dependencies to configuring environment variables.
#!/bin/bash
# A script to set up a complete Android and Flutter development environment on a Debian-based Linux system.
# It installs dependencies, Java, Node.js, the Android SDK (with dynamic package discovery), and Flutter.
set -e
set -o pipefail
# --- Configuration ---
SDK_ROOT="$HOME/Android/Sdk"
FLUTTER_DIR="$HOME/flutter"
REPO_URL="https://dl.google.com/android/repository/repository2-1.xml"
PROFILE_FILE="$HOME/.bashrc"
if [ -f "$HOME/.zshrc" ]; then
PROFILE_FILE="$HOME/.zshrc"
fi
# --- OS Detection ---
OS_NAME=""
case "$(uname -s)" in
Linux*) OS_NAME="linux";;
Darwin*) OS_NAME="macosx";;
*) echo "Unsupported OS: $(uname -s). This script currently supports Debian/Ubuntu Linux and macOS."; exit 1;;
esac
# --- Helper Functions ---
# Function to print colored text
print_info() {
echo -e "\n\e[34m[INFO]\e[0m $1"
}
print_success() {
echo -e "\e[32m[SUCCESS]\e[0m $1"
}
print_error() {
echo -e "\e[31m[ERROR]\e[0m $1" >&2
}
# Add a line to the user's shell profile file if it's not already there.
add_to_profile() {
local line="$1"
if ! grep -qF -- "$line" "$PROFILE_FILE"; then
print_info "Adding to profile: $line"
echo -e "\n# Added by dev setup script\n$line" >> "$PROFILE_FILE"
else
echo "[SKIPPED] '$line' already exists in $PROFILE_FILE."
fi
}
# Find the latest version string (e.g., "34.0.0") for a package prefix.
get_latest_version_id() {
local package_path_prefix="$1"
local xml_file="$2"
# The XPath finds all non-obsolete packages for the current OS that match the prefix.
# It then extracts the version part from the path, sorts it version-wise, and gets the latest.
latest_version=$(xmllint --xpath "//remotePackage[starts-with(@path, '${package_path_prefix};') and not(@obsolete) and .//archive[host-os='${OS_NAME}']]" "$xml_file" | \
grep -o 'path="[^"]*"' | \
sed -e "s/path=\"${package_path_prefix};//" -e 's/"//' | \
sort -V | tail -n 1)
if [ -z "$latest_version" ]; then
print_error "Could not find any version for package prefix '$package_path_prefix' for OS '$OS_NAME'."
return 1
fi
echo "${package_path_prefix};${latest_version}"
}
# Get the download URL for a package that has a fixed path in the repository XML.
get_fixed_path_url() {
local package_path="$1"
local xml_file="$2"
url=$(xmllint --xpath "string(//remotePackage[@path='${package_path}' and not(@obsolete) and .//archive[host-os='${OS_NAME}']]//archive[host-os='${OS_NAME}']/complete/url)" "$xml_file")
if [ -z "$url" ]; then
print_error "Could not find download URL for package '$package_path'."
return 1
fi
echo "$url"
}
# --- Main Execution Flow ---
main() {
# STEP 1: SYSTEM DEPENDENCIES
if [[ "$OS_NAME" == "linux" ]]; then
print_info "Updating package lists and installing foundational dependencies for Linux..."
sudo apt-get update -y
sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa clang cmake ninja-build pkg-config \
libgtk-3-dev liblzma-dev libstdc++-12-dev libxml2-utils openjdk-17-jdk
else
print_info "Please ensure Xcode Command Line Tools, curl, git, and unzip are installed."
fi
# STEP 2: JAVA DEVELOPMENT KIT (JDK)
print_info "Configuring JAVA_HOME..."
if [[ "$OS_NAME" == "linux" ]]; then
JAVA_HOME_PATH=$(update-java-alternatives -l | grep '1.17' | head -n 1 | awk '{print $3}')
if [ -z "$JAVA_HOME_PATH" ]; then
print_error "Could not determine JAVA_HOME path for OpenJDK 17."
exit 1
fi
add_to_profile "export JAVA_HOME=$JAVA_HOME_PATH"
export JAVA_HOME="$JAVA_HOME_PATH"
print_success "JAVA_HOME set to $JAVA_HOME"
else
print_info "On macOS, JAVA_HOME is typically managed by the system. Skipping manual configuration."
fi
# STEP 3: NODE.JS with NVM (Optional but common for modern dev)
print_info "Installing Node Version Manager (NVM) and latest LTS Node.js..."
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/refs/heads/master/install.sh | bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # Source NVM for the current session
nvm install --lts
nvm alias default 'lts/*'
nvm use default
add_to_profile 'export NVM_DIR="$HOME/.nvm"'
add_to_profile '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm'
# STEP 4: ANDROID SDK
print_info "Setting up Android SDK in $SDK_ROOT..."
mkdir -p "$SDK_ROOT"
# Download repository index to find the command-line tools URL
XML_FILE=$(mktemp)
trap 'rm -f "$XML_FILE"' EXIT # Ensure temp file is cleaned up
print_info "Downloading Android SDK repository index..."
if ! curl -s -L -o "$XML_FILE" "$REPO_URL"; then
print_error "Failed to download repository index. Exiting."
exit 1
fi
print_success "Repository index downloaded."
# Dynamically find the command-line tools URL
CMDLINE_TOOLS_FILENAME=$(get_fixed_path_url 'cmdline-tools;latest' "$XML_FILE")
if [ -z "$CMDLINE_TOOLS_FILENAME" ]; then
print_error "Could not determine command-line tools URL. Exiting."
exit 1
fi
CMDLINE_TOOLS_URL="https://dl.google.com/android/repository/${CMDLINE_TOOLS_FILENAME}"
# Download and set up command-line tools
CMDLINE_TOOLS_ZIP="$HOME/cmdline-tools.zip"
print_info "Downloading Android command-line tools..."
curl -# -L "$CMDLINE_TOOLS_URL" -o "$CMDLINE_TOOLS_ZIP"
print_info "Installing command-line tools..."
# FIX: Ensure the final destination directory exists before trying to move files.
mkdir -p "$SDK_ROOT/cmdline-tools/latest"
# Unzip into a temporary folder, then move the contents into the final destination.
unzip -q -o "$CMDLINE_TOOLS_ZIP" -d "$SDK_ROOT/cmdline-tools_temp"
# The zip contains a 'cmdline-tools' directory, move its contents to 'latest'
mv "$SDK_ROOT/cmdline-tools_temp/cmdline-tools"/* "$SDK_ROOT/cmdline-tools/latest/"
# Cleanup temporary files
rm -rf "$SDK_ROOT/cmdline-tools_temp"
rm "$CMDLINE_TOOLS_ZIP"
# Add SDK paths to the current session's PATH to use sdkmanager immediately
export ANDROID_HOME="$SDK_ROOT"
export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator"
# Add to profile for future sessions
add_to_profile "export ANDROID_HOME=$SDK_ROOT"
add_to_profile 'export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin"'
add_to_profile 'export PATH="$PATH:$ANDROID_HOME/platform-tools"'
add_to_profile 'export PATH="$PATH:$ANDROID_HOME/emulator"'
# Find latest package versions using the downloaded XML
print_info "Finding latest SDK package versions..."
LATEST_BUILD_TOOLS=$(get_latest_version_id 'build-tools' "$XML_FILE")
LATEST_CMAKE=$(get_latest_version_id 'cmake' "$XML_FILE")
LATEST_NDK=$(get_latest_version_id 'ndk' "$XML_FILE")
print_success "Latest build-tools: $LATEST_BUILD_TOOLS"
print_success "Latest CMake: $LATEST_CMAKE"
print_success "Latest NDK: $LATEST_NDK"
print_info "Installing core Android SDK packages using sdkmanager..."
yes | sdkmanager --licenses > /dev/null # Accept all licenses
sdkmanager --update
sdkmanager --install \
"platform-tools" \
"$LATEST_BUILD_TOOLS" \
"$LATEST_CMAKE" \
"$LATEST_NDK" \
"platforms;android-34" \
"system-images;android-34;google_apis;x86_64" \
"emulator"
# Add CMAKE and create NDK bundle link
LATEST_CMAKE_VER=$(echo "$LATEST_CMAKE" | cut -d';' -f2)
add_to_profile 'export PATH="$PATH:$ANDROID_HOME/cmake/'"$LATEST_CMAKE_VER"'/bin"'
# For compatibility, create an 'ndk-bundle' symlink pointing to the installed NDK version
LATEST_NDK_VER=$(echo "$LATEST_NDK" | cut -d';' -f2)
if [ -d "$SDK_ROOT/ndk/$LATEST_NDK_VER" ]; then
ln -sfn "$SDK_ROOT/ndk/$LATEST_NDK_VER" "$SDK_ROOT/ndk-bundle"
add_to_profile 'export PATH="$PATH:$ANDROID_HOME/ndk-bundle"'
fi
# STEP 5: FLUTTER SDK
print_info "Installing Flutter SDK to $FLUTTER_DIR..."
if [ -d "$FLUTTER_DIR" ]; then
print_info "Flutter directory exists. Updating..."
(cd "$FLUTTER_DIR" && git pull)
else
git clone https://github.com/flutter/flutter.git -b stable "$FLUTTER_DIR"
fi
add_to_profile "export PATH=\"\$PATH:$FLUTTER_DIR/bin\""
export PATH="$PATH:$FLUTTER_DIR/bin"
# STEP 6: FINALIZATION
print_info "Finalizing Flutter Configuration..."
flutter config --android-sdk "$SDK_ROOT"
flutter precache
flutter doctor --android-licenses
print_info "Disabling Flutter analytics..."
flutter --disable-analytics
echo ""
echo "========================================================"
echo " Development Environment Setup Complete!"
echo " To apply all changes, run: source $PROFILE_FILE"
echo " OR open a new terminal."
echo " Then, run 'flutter doctor' to verify the setup."
echo "========================================================"
echo ""
}
main
echo ""
echo "========================================================"
echo " Development Environment Setup Complete!"
echo " To activate fully, run: source ~/.bashrc"
echo " OR open a new terminal."
echo " Then, run 'flutter doctor' to verify."
echo "========================================================"
echo ""
Anatomy of the Setup
Curious about what the script is doing under the hood? Click on each step in the process below to reveal a detailed explanation of its purpose and the components involved. This interactive diagram helps you understand the dependencies and logic behind each phase of the installation.
System Priming & Foundational Dependencies
This initial step prepares your system by installing a curated set of libraries and tools that the Flutter and Android toolchains depend on. Without these, compilation and tool execution would fail. It includes everything from download utilities (`curl`, `wget`) to the C/C++ build system (`clang`, `cmake`) needed by the Flutter engine. It also installs `libxml2-utils` which provides the `xmllint` command for parsing the Android SDK repository.
| Package | Purpose |
|---|---|
| git, curl, wget, unzip | Fetching and extracting SDK archives. |
| clang, cmake, ninja-build | C/C++ toolchain for compiling native Flutter code. |
| libgtk-3-dev | GTK+ libraries for building Linux desktop apps. |
| libglx-mesa0 | OpenGL libraries for the Android Emulator. |
| libxml2-utils | Provides the `xmllint` command for parsing XML files. |
Java Development Kit (JDK) Configuration
The Android build system (Gradle) requires a specific version of the JDK to function correctly. This step installs `openjdk-17-jdk`, a stable Long-Term Support (LTS) release. It then automatically finds the installation path and sets the crucial `JAVA_HOME` environment variable, which tells tools like Gradle where to find the Java installation.
Node.js Management with NVM
Instead of a system-wide install, the script uses Node Version Manager (NVM), a professional best practice. NVM installs Node.js in your home directory, avoiding permission issues and allowing you to easily install and switch between different Node versions (`nvm use 18`, `nvm use 20`, etc.). The script installs the latest LTS version and sets it as the default.
Android Command-Line Toolchain
This hybrid approach offers the best of both worlds. It first downloads only the command-line tools. It then uses `xmllint` to parse the official repository XML, dynamically finding the package IDs for the absolute latest `build-tools`, `cmake`, and `ndk`. Finally, it uses the official `sdkmanager` tool to robustly install these latest packages, ensuring proper registration and license handling.
Flutter SDK Deployment & PATH Configuration
The script clones the `stable` branch of Flutter from its official Git repository, giving you the latest stable release. Crucially, it then adds the Flutter SDK's `bin` directory to your system's `PATH` by modifying the `~/.profile` file. This is what makes the `flutter` command available from anywhere in your terminal. The script also does the same for the Android SDK and NVM, unifying the entire environment.
Verification Checklist
After running the script and opening a new terminal (or running `source ~/.bashrc`), use this checklist to verify that every component is installed and configured correctly. The ultimate test is running `flutter doctor`.
What's Next?
Your environment is ready. Here are the recommended next steps to start developing. Integrate with your favorite IDE and create a virtual device for testing your applications.
1. IDE Integration (VS Code)
Install the official Flutter and Dart extensions from the Visual Studio Code Marketplace. They will automatically detect your new SDK setup.
2. Create an Android Emulator
The script installed an emulator image. Use the command below to create a virtual device named `pixel_34_api`. You can then launch it from Android Studio or the command line.
avdmanager create avd -n pixel_34_api -k "system-images;android-34;google_apis;x86_64"