Get Flutter installed, learn Dart fundamentals, and write your first Flutter app from scratch.
Flutter is Google's open-source UI framework for building natively compiled applications from a single Dart codebase. One codebase targets all platforms:
| Platform | Output | Notes |
|---|---|---|
| Android | APK / AAB | Android 5.0 (API 21) and above |
| iOS | IPA | Requires macOS and Xcode to build |
| Web | HTML / JS / WASM | CanvasKit or HTML renderer |
| Windows | .exe | Stable since Flutter 3.0 |
| macOS | .app | Stable since Flutter 3.0 |
| Linux | Binary | Stable since Flutter 3.0 |
Flutter renders via its own Skia / Impeller engine — no WebView, no native UI widgets called. Every pixel is drawn by Flutter itself, giving pixel-perfect consistency across platforms.
C:\flutter (avoid spaces in the path)C:\flutter\bin to your system PATH environment variableflutter doctor# Check Flutter installation and environment flutter doctor # Verbose output (more details for troubleshooting) flutter doctor -v # Accept Android SDK licenses flutter doctor --android-licenses # Upgrade Flutter to latest stable flutter upgrade # Switch to a specific channel flutter channel stable
[✓] Flutter (Channel stable, 3.x.x, on Windows 11)
[✓] Windows Version (Windows 11)
[✓] Android toolchain - develop for Android devices
• Android SDK at C:\Users\You\AppData\Local\Android\sdk
• Platform android-34, build-tools 34.0.0
[✓] Chrome - develop for the web
[✓] Visual Studio - develop Windows apps (Visual Studio 2022)
[✓] VS Code (version 1.90.x)
• Flutter extension version 3.x.x
[✓] Connected device (3 available)
[✓] Network resources
flutter doctor --android-licenses to accept licenses.VS Code is the recommended lightweight editor for Flutter development. It gives you IntelliSense, hot reload, and widget inspection.
Ctrl+Shift+X)| Shortcut | Action |
|---|---|
F5 | Run / Debug app |
Ctrl+Shift+P → "Flutter: New Project" | Create a new Flutter project |
Ctrl+. | Quick fix / wrap with widget |
r in debug terminal | Hot reload (preserves state) |
R in debug terminal | Hot restart (resets state) |
Alt+Shift+F | Format Dart file |
Ctrl+Shift+P → "Dart: Open DevTools" | Open Flutter DevTools (widget inspector, performance) |
# Create a new Flutter project flutter create my_app # Navigate into it cd my_app # List available devices/emulators flutter devices # Run on default device flutter run # Run on Chrome (web) flutter run -d chrome # Run on Windows desktop flutter run -d windows # Build release APK flutter build apk --release
my_app/
android/ -- Android-specific files (gradle, manifests)
ios/ -- iOS-specific files (Xcode project)
lib/
main.dart -- YOUR APP STARTS HERE (entry point)
test/
widget_test.dart
pubspec.yaml -- Dependencies and assets (like package.json)
pubspec.lock -- Lock file (do not edit manually)
web/ -- Web-specific files
windows/ -- Windows-specific files
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My Flutter App',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorSchemeSeed: Colors.blue,
useMaterial3: true,
),
home: Scaffold(
appBar: AppBar(title: const Text('Hello Flutter')),
body: const Center(child: Text('Welcome!')),
),
);
}
}
Dart is a strongly-typed, object-oriented language. Type inference lets you skip writing the type when it can be inferred.
// Type inference — Dart figures out the type
var name = 'Alice'; // String
var age = 30; // int
var price = 9.99; // double
var isReady = true; // bool
// Explicit types (same result, more readable)
String city = 'Yangon';
int count = 100;
double temperature = 36.5;
bool isDone = false;
// final — set once at runtime
final createdAt = DateTime.now(); // OK, evaluated at runtime
// createdAt = DateTime.now(); // ERROR — cannot reassign
// const — compile-time constant (must be known at compile time)
const pi = 3.14159;
const appName = 'MyApp';
// ---- Collections ----
// List (ordered, like an array)
List<String> fruits = ['apple', 'banana', 'mango'];
fruits.add('grape');
fruits.remove('banana');
fruits.insert(0, 'cherry');
print(fruits.length); // 3
print(fruits[0]); // cherry
print(fruits.first); // cherry
print(fruits.last); // grape
print(fruits.contains('mango')); // true
// Map (key-value pairs)
Map<String, dynamic> user = {
'name': 'Bob',
'age': 25,
'email': 'bob@example.com',
};
print(user['name']); // Bob
user['phone'] = '09123456'; // add
user.remove('email'); // remove
print(user.keys.toList()); // [name, age, phone]
print(user.containsKey('age')); // true
// Set (unique values only)
Set<int> ids = {1, 2, 3, 2, 1};
print(ids); // {1, 2, 3} — duplicates removed
dynamic turns off type checking for a variable. Prefer specific types — dynamic defeats null safety and autocomplete.Dart has sound null safety since Dart 2.12. By default, variables cannot be null. You explicitly opt in to nullability with ?.
// Non-nullable (default) — cannot hold null
String name = 'Alice';
// name = null; // COMPILE ERROR
// Nullable — add ? to allow null
String? nickname; // defaults to null
nickname = 'Ali';
nickname = null; // OK
// Null-aware operators
String? city;
// ?? — null coalescing: use right side if left is null
String display = city ?? 'Unknown City';
print(display); // Unknown City
// ??= — assign only if currently null
city ??= 'Yangon'; // sets city because it was null
city ??= 'Mandalay'; // does NOT change — already set
// ?. — null-safe member access (returns null if object is null)
String? upper = city?.toUpperCase(); // 'YANGON'
int? len = city?.length; // 6
// ! — force-unwrap (throws Null check error if null — use carefully)
String knownCity = city!; // asserts non-null
// Late — non-nullable but initialized later (use carefully)
late String token;
// ... after some async work ...
token = 'abc123';
print(token); // safe to use now
// Null safety in functions
void printLength(String? s) {
if (s == null) return;
print(s.length); // Dart knows s is non-null here (smart cast)
}
?? for defaults, ?. for safe member access, and avoid ! unless you are 100% certain the value is non-null.// Basic function with return type
int add(int a, int b) {
return a + b;
}
// Arrow function — for single expressions only
int multiply(int a, int b) => a * b;
String greetDefault(String name) => 'Hello, $name!';
// Optional positional parameters (in square brackets, with default value)
String greet(String name, [String greeting = 'Hello']) {
return '$greeting, $name!';
}
print(greet('Alice')); // Hello, Alice!
print(greet('Bob', 'Hi')); // Hi, Bob!
// Named parameters (in curly braces)
// required means the caller MUST provide the value
void createUser({
required String name,
required String email,
int age = 0, // optional with default
bool isAdmin = false,
}) {
print('$name | $email | age:$age | admin:$isAdmin');
}
createUser(name: 'Alice', email: 'a@b.com');
createUser(name: 'Bob', email: 'b@c.com', age: 30, isAdmin: true);
// Functions as first-class values
var double = (int n) => n * 2;
print(double(5)); // 10
// Passing functions as arguments
void runTwice(Function action) {
action();
action();
}
runTwice(() => print('Running!'));
// Higher-order collection methods
List<int> nums = [1, 2, 3, 4, 5];
var doubled = nums.map((n) => n * 2).toList(); // [2,4,6,8,10]
var evens = nums.where((n) => n.isEven).toList(); // [2, 4]
var sum = nums.fold(0, (acc, n) => acc + n); // 15
var any2 = nums.any((n) => n > 4); // true
var all2 = nums.every((n) => n > 0); // true
// Async / Await
Future<String> fetchData() async {
await Future.delayed(const Duration(seconds: 1));
return 'data loaded';
}
// Calling async
void main() async {
String result = await fetchData();
print(result); // data loaded
}
// Stream (continuous async events)
Stream<int> countUp() async* {
for (int i = 1; i <= 5; i++) {
await Future.delayed(const Duration(seconds: 1));
yield i;
}
}
// Listen: countUp().listen((n) => print(n));
// Basic class
class Person {
String name;
int age;
// Primary constructor using shorthand (this.name sets the field)
Person(this.name, this.age);
// Named constructor
Person.guest() : name = 'Guest', age = 0;
// Factory constructor — can return cached instance or subtype
factory Person.fromJson(Map<String, dynamic> json) {
return Person(json['name'] as String, json['age'] as int);
}
// Instance method
void introduce() {
print('I am $name, age $age.');
}
// Getter (computed property)
bool get isAdult => age >= 18;
// Setter
set fullName(String value) {
name = value.split(' ').first;
}
@override
String toString() => 'Person($name, $age)';
}
// --- Usage ---
var p1 = Person('Alice', 28);
var p2 = Person.guest();
var p3 = Person.fromJson({'name': 'Bob', 'age': 22});
p1.introduce(); // I am Alice, age 28.
print(p1.isAdult); // true
print(p2); // Person(Guest, 0)
// Inheritance
class Employee extends Person {
String company;
Employee(String name, int age, this.company) : super(name, age);
@override
void introduce() {
super.introduce();
print('I work at $company.');
}
}
// Abstract class — blueprint that cannot be instantiated directly
abstract class Shape {
double area(); // subclasses must implement this
double perimeter();
}
class Circle extends Shape {
double radius;
Circle(this.radius);
@override
double area() => 3.14159 * radius * radius;
@override
double perimeter() => 2 * 3.14159 * radius;
}
// Mixin — add capabilities without full inheritance
mixin CanFly {
void fly() => print('Flying!');
}
mixin CanSwim {
void swim() => print('Swimming!');
}
class Duck extends Animal with CanFly, CanSwim {}
// Duck can both fly() and swim()
// Enum
enum Status { pending, active, inactive, deleted }
var s = Status.active;
print(s.name); // active