Flutter compiles one Dart codebase to Android, iOS, and web targets. Setup takes four steps: install the Flutter SDK, install each platform toolchain, confirm everything with flutter doctor, then run flutter create. The SDK install is quick. Most failures come from the Android SDK licenses, Xcode, or CocoaPods, so this guide covers those in detail. At the time of writing, the stable channel is at Flutter 3.44 with Dart 3.12.
Quick Takeaways
- Install the SDK through the VS Code Flutter extension or manually, then verify with
flutter doctor -v. - Android needs Android Studio, the SDK command-line tools, and accepted licenses. iOS needs macOS, Xcode, and CocoaPods. Web needs only Chrome.
- One command,
flutter create, scaffolds all three platforms.flutter run -d <device>launches any of them. - Run
flutter doctorafter every toolchain change. It reports what is missing.
| Target | Host OS | Required Toolchain | Run Command |
|---|---|---|---|
| Web | Windows, macOS, Linux | Chrome | flutter run -d chrome |
| Android | Windows, macOS, Linux | Android Studio + SDK | flutter run -d <emulator-id> |
| iOS | macOS only | Xcode + CocoaPods | flutter run -d <simulator-id> |
System Requirements
Flutter supports 64-bit Windows 10 and 11, macOS, and Linux. Plan for 10 GB or more of free disk space once Android Studio and its emulator images are installed. Xcode needs more.
Install these first:
- Git: the SDK is a Git checkout, and
flutter upgradedepends on it. - VS Code with the official Flutter extension, which also installs the Dart extension. Any Code OSS-based editor that supports VS Code extensions also works.
- Google Chrome: the default web debug target.
On Linux, install the desktop build dependencies as well:
# Debian/Ubuntu: toolchain used by Flutter's native Linux builds
sudo apt-get install clang cmake git ninja-build pkg-config \
libgtk-3-dev liblzma-dev libstdc++-12-dev
Never run Flutter commands with sudo. It creates permission problems in the SDK directory that are tedious to undo.
Install the Flutter SDK
There are two supported routes. Choose one.
Option 1: Install with the VS Code extension
This route downloads the SDK and adds it to your PATH for you.
- Open VS Code and install the Flutter extension from the Marketplace.
- Open the Command Palette (Ctrl/Cmd + Shift + P).
- Run Flutter: New Project. If no SDK is found, VS Code offers to download one.
- Pick an install folder such as
~/devorC:\dev. Avoid paths with spaces and avoidC:\Program Files. - Accept the prompt to add the SDK to PATH.
- Close and reopen all terminals, then restart VS Code.
Option 2: Install manually
Download the SDK archive from the Flutter site, extract it, and add its bin directory to PATH.
# macOS/Linux: add the SDK to PATH (zsh example)
echo 'export PATH="$HOME/dev/flutter/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
# Confirm the binary resolves
which flutter
flutter --version
On Windows, add %USERPROFILE%\dev\flutter\bin to the user Path environment variable. Move it to the top of the list so no older install shadows it.
flutter --version prints the framework version, the channel, and the bundled Dart version. The first run also downloads the engine artifacts, so expect a short delay.
Choose a channel
| Channel | Stability | Use Case |
|---|---|---|
| stable | Production-tested, quarterly releases | Shipping apps (default) |
| beta | Pre-release candidate | Testing upcoming changes |
| main | Unreviewed daily builds | Contributing to the framework |
Stay on stable unless you have a specific reason to switch. Many teams run one release behind the newest stable so package maintainers have time to catch up.
Verify with flutter doctor
flutter doctor -v
The -v flag prints paths, versions, and the exact fix for each failure. A fresh install looks like this:
[✓] Flutter (Channel stable, 3.44.0)
[!] Android toolchain - develop for Android devices
[!] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] VS Code
[✓] Connected device (2 available)
[✓] Network resources
A [!] marks a fixable gap. A [✗] marks a blocker. Ignore Android or Xcode warnings until you configure those platforms. Re-run flutter doctor after each step below.
Configure Android
Android works on all three host operating systems.
- Install Android Studio.
- Open it and finish the setup wizard. It downloads the Android SDK and platform tools.
- Go to Settings → Languages & Frameworks → Android SDK → SDK Tools. Enable Android SDK Command-line Tools (latest).
- Accept the licenses:
# Prompts for each SDK license; answer "y" to all
flutter doctor --android-licenses
- Create a virtual device in Device Manager. Choose a recent Pixel profile and a current system image that matches your CPU architecture. Enable hardware acceleration in your BIOS if the emulator is slow.
List and start emulators from the terminal:
flutter emulators # list available AVDs
flutter emulators --launch Pixel_8 # boot one by id
flutter devices # confirm Flutter sees it
To use a physical phone, enable Developer options and USB debugging, connect it, and approve the RSA fingerprint prompt. It then appears in flutter devices.
If flutter doctor reports cmdline-tools component is missing, step 3 is the cause. If it reports an unknown Java or SDK location, point Flutter at the right one:
flutter config --android-sdk "/path/to/Android/sdk"
Configure iOS
iOS builds require a Mac. Windows and Linux cannot compile for iOS or run the Simulator.
- Install Xcode from the Mac App Store.
- Select it as the active developer directory and accept the license:
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunch
- Install the iOS platform runtime when Xcode prompts for it, or add it under Xcode → Settings → Components.
- Install CocoaPods, which Flutter uses to manage native iOS dependencies:
brew install cocoapods
- Open the Simulator:
open -a Simulator
flutter devices # the simulator should now be listed
Running on a physical iPhone also requires a free Apple ID added under Xcode → Settings → Accounts. Set a signing team on the Runner target before the first device build. App Store distribution requires a paid Apple Developer Program membership.
Configure Web
Web needs nothing extra beyond Chrome. Confirm Flutter sees it:
flutter devices
# Chrome (web) • chrome • web-javascript • Google Chrome ...
If Chrome sits in a non-standard location, set the CHROME_EXECUTABLE environment variable to its path.
Create Your First App
Scaffold the project and target all three platforms explicitly:
flutter create --platforms=android,ios,web my_first_app
cd my_first_app
flutter create generates this structure:
| Path | Purpose |
|---|---|
lib/main.dart |
App entry point and your Dart code |
pubspec.yaml |
Dependencies, assets, and SDK constraints |
android/ |
Gradle project for Android builds |
ios/ |
Xcode project for iOS builds |
web/ |
index.html and web manifest |
test/ |
Widget tests |
Directories only appear for platforms you selected. Add another later with flutter create --platforms=web . from inside the project.
Replace the starter code
The default counter demo works, but a minimal app shows the core model more clearly. Replace lib/main.dart with:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
/// Root widget: configures theme and the home screen.
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My First Flutter App',
theme: ThemeData(
colorSchemeSeed: Colors.indigo,
useMaterial3: true,
),
home: const CounterPage(),
);
}
}
/// Stateful widget: owns a mutable counter.
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _count = 0;
// setState() schedules a rebuild so the UI reflects the new value.
void _increment() => setState(() => _count++);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Hello, Flutter')),
body: Center(
child: Text(
'Taps: $_count',
style: Theme.of(context).textTheme.headlineMedium,
),
),
floatingActionButton: FloatingActionButton(
onPressed: _increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
The app shows a centered label and a floating button. Each tap calls setState(), which marks the widget dirty. Flutter then re-runs build() and redraws only what changed. StatelessWidget holds no mutable data. StatefulWidget keeps its data in a separate State object that survives rebuilds.
Run on Android, iOS, and Web
List every available target, then pass the id with -d:
flutter devices
flutter run -d chrome # web
flutter run -d emulator-5554 # Android emulator
flutter run -d "iPhone 16" # iOS Simulator (macOS only)
The first Android build downloads Gradle and its dependencies. Expect several minutes. Later builds are much faster.
While flutter run is active, the terminal accepts these keys:
| Key | Action | Preserves State |
|---|---|---|
r |
Hot reload: injects changed code | Yes |
R |
Hot restart: restarts the app | No |
q |
Quit | n/a |
Change 'Taps: $_count' to another string and press r. The UI updates in about a second and the counter keeps its value. Hot reload does not re-run initState() or changed global initializers, so use a hot restart for those.
Build release artifacts
flutter build web --release # output: build/web/
flutter build apk --release # output: build/app/outputs/flutter-apk/
flutter build appbundle # Play Store upload format (.aab)
flutter build ipa # iOS archive (macOS, signing required)
The build/web/ folder is static. Host it on any static file server.
Common Setup Errors and Fixes
| Symptom | Cause | Fix |
|---|---|---|
Unable to locate Android SDK |
SDK path not set | flutter config --android-sdk <path> |
Android license status unknown |
Licenses not accepted | flutter doctor --android-licenses |
cmdline-tools component is missing |
Tools not installed | Enable in SDK Manager → SDK Tools |
CocoaPods not installed |
Missing dependency | brew install cocoapods |
flutter: command not found |
PATH not updated | Add flutter/bin to PATH, reopen terminal |
| Emulator never appears | Acceleration disabled | Enable virtualization in BIOS/UEFI |
Best Practices and Refactoring
Early Flutter projects tend to collect the same mistakes. Two of the most common are below.
Anti-pattern: one giant build method and unscoped rebuilds
// BAD: a single huge build() that rebuilds everything on every tap
class _BadPageState extends State<BadPage> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
// Static header rebuilt on every setState
Container(
padding: EdgeInsets.all(16),
child: Text('Dashboard', style: TextStyle(fontSize: 24)),
),
Text('$_count'),
ElevatedButton(
onPressed: () => setState(() => _count++),
child: Text('Add'),
),
],
),
);
}
}
This version gets four things wrong. It omits const on widgets that never change. It places static UI inside a rebuilt tree. It hard-codes a text style instead of reading the theme. It also mixes layout and state in one method.
Refactored: extracted widgets and const constructors
// GOOD: static parts are const widgets Flutter can skip on rebuild
class _GoodPageState extends State<GoodPage> {
int _count = 0;
void _increment() => setState(() => _count++);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
const _Header(), // never rebuilds
_CountLabel(count: _count), // rebuilds only when count changes
FilledButton(
onPressed: _increment,
child: const Text('Add'),
),
],
),
);
}
}
class _Header extends StatelessWidget {
const _Header();
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.all(16),
child: Text(
'Dashboard',
style: Theme.of(context).textTheme.headlineSmall,
),
);
}
class _CountLabel extends StatelessWidget {
const _CountLabel({required this.count});
final int count;
@override
Widget build(BuildContext context) => Text('$count');
}
A const widget is built once at compile time, so Flutter can skip it during a rebuild. Extracted widgets keep each build() short and testable. Reading styles from Theme.of(context) keeps light and dark modes consistent.
Habits worth keeping from day one
- Run
flutter analyzebefore each commit. It catches missingconst, unused imports, and unsafeBuildContextuse across async gaps. - Run
dart format .to keep formatting uniform. - Commit
pubspec.lockfor apps. Do not commit it for published packages. - Run
flutter pub outdatedregularly, andflutter upgradeonly between feature branches. - Pin the SDK version in CI so local and build-server results match.
Keep the SDK Updated
flutter upgrade # move to the newest release on your channel
flutter channel stable # confirm or switch channel
flutter clean # delete build/ and .dart_tool/ when builds act strangely
flutter pub get # re-resolve dependencies after pulling changes
After a major upgrade, run flutter doctor again and rebuild each platform once. Native dependency versions, such as Gradle and CocoaPods, sometimes change between releases.
FAQ
How do I install Flutter on Windows, macOS, or Linux?
Install Git and VS Code, add the Flutter extension, and run Flutter: New Project from the Command Palette. VS Code downloads the SDK and offers to add it to PATH. You can also extract the SDK archive manually and add its bin folder to PATH. Verify with flutter doctor -v.
Can I build an iOS app on Windows or Linux?
No. iOS builds need Xcode, which runs only on macOS. On Windows or Linux you can develop for Android and web. To ship an iOS app, build on a Mac or use a macOS-based CI service.
What does flutter doctor check?
It checks the Flutter SDK, the Android toolchain and licenses, Xcode and CocoaPods on macOS, Chrome for web, your IDE plugins, connected devices, and network access. Run flutter doctor -v for detailed paths and fixes.
Do I need to know Dart before learning Flutter?
No. If you know Java, Kotlin, Swift, C#, or TypeScript, you can read Dart quickly. Learn null safety, async/await, and Future and Stream early, since Flutter code uses them constantly.




