Cheatsheet Flutter
Framework para desenvolvimento mobile
Flutter
Basic Syntax (Dart)
Variables
int age = 30; double price = 9.99; String name = "Anna"; bool active = true; var total = 100; // inferred type (int) dynamic x = "text"; // accepts any type
Use var when you want Dart to infer the type. dynamic disables type checking and should be avoided in production code.
String interpolation
String name = "Anna";
int age = 30;
String s = "Hello $name";
String r = "Total: ${price * 2}";
String m = "Name: ${name.toUpperCase()}";Use $variable to insert simple values and ${expressao} for expressions or method calls inside the string.
Null-aware operators
String? name; name ??= "Anonymous"; // assigns if null var x = name ?? "N/D"; // fallback // Safe chained access int? len = name?.length; var city = user?.address?.city;
??= assigns only if the variable is null. ?. breaks the chain and returns null instead of throwing an exception.
late (lazy initialization)
class Service {
late final ApiClient api;
Service() {
api = ApiClient(); // initialized later
}
}
late String config = loadConfig();late lets you declare a non-null variable that will be initialized later. Dart only checks the assignment on first access.
final and const
final name = "Anna"; // assigned once (runtime) const pi = 3.14; // compile-time constant final list = [1, 2]; list.add(3); // OK: the content can change // list = [4]; // ERROR: cannot reassign
final allows a single assignment at runtime. const is immutable from compile time and must have a statically known value.
String methods
String s = " Hello World ";
s.trim(); // "Hello World"
s.toLowerCase(); // lowercase
s.toUpperCase(); // uppercase
s.length; // length
s.contains("World"); // true
s.split(" "); // list of words
s.replaceAll("o", "0"); // replaceStrings are immutable: each method returns a new string. trim() removes spaces and split() returns a List<String>.
Cascade (..)
var list = [] ..add(1) ..add(2) ..add(3) ..sort(); var paint = Paint() ..color = Colors.blue ..strokeWidth = 2.0;
The .. operator chains several operations on the same object without repeating its name. Very useful for configuring objects compactly.
Type aliases (typedef)
typedef Callback = void Function(String);
typedef Even = (int, int);
void register(Callback cb) {
cb("ok");
}
register((msg) => print(msg));typedef creates an alternative name for a type, useful for complex function signatures. In Dart 3 it also defines named records.
Data types
int n = 10; // integer
double d = 3.14; // decimal
num x = 5; // int or double
String s = "text"; // text
bool b = true; // boolean
List<int> l = [1, 2]; // list
Map<String, int> m = {}; // map
Set<int> c = {1, 2}; // setDart is strongly typed. num is the superclass of int and double. Collections use generics like List<int>.
Arithmetic operators
int a = 10, b = 3; a + b; // 13 addition a - b; // 7 subtraction a * b; // 30 multiplication a / b; // 3.33 division (double) a ~/ b; // 3 integer division a % b; // 1 remainder (module) a++; // increment
Division / always returns a double. Use ~/ to get the integer quotient and % for the division remainder.
Type conversion
int n = int.parse("42");
double d = double.parse("3.14");
String s = n.toString();
String f = 3.14.toStringAsFixed(1); // "3.1"
int? error = int.tryParse("abc"); // null (no exception)int.parse() and double.parse() convert strings into numbers and throw an exception if invalid. tryParse() returns null instead of failing.
Null safety
String? name; // accepts null name ??= "Anonymous"; // assigns if null int len = name!.length; // forces non-null String s = name ?? "default"; // alternative value String? null = null; null?.length; // returns null, not an error
By default types do not accept null. The ? makes the type nullable, ?? provides a fallback and ?. avoids exceptions in chains.
Comparison and logical
// Comparison 5 == 5; // true 5 != 3; // true 5 > 3; // true 5 <= 5; // true // Logical true && false; // false (AND) true || false; // true (OR) !true; // false (negation)
The && (AND) and || (OR) operators are short-circuiting: the second operand is only evaluated if necessary. ! negates a bool.
Numbers (int and double)
double d = 9.876; d.round(); // 10 d.floor(); // 9 d.ceil(); // 10 d.toStringAsFixed(2); // "9.88" d.abs(); // absolute value int n = -5; n.abs(); // 5 n.isEven; // false
The round(), floor() and ceil() methods round in different ways. toStringAsFixed() formats decimal places.
Flow Control
if / else
if (age >= 18) {
print("adult");
} else if (age > 12) {
print("young");
} else {
print("child");
}The if/else if/else structure evaluates conditions in order and runs the first true block. The parentheses are mandatory.
classic for
for (int i = 0; i < 10; i++) {
print(i);
}
for (int i = 10; i > 0; i--) {
print(i); // countdown
}The for loop has initialization, condition and increment. It's ideal when you need the index or to control the counter manually.
break and continue
for (int i = 0; i < 10; i++) {
if (i == 3) continue; // skips 3
if (i == 7) break; // stops at 7
print(i);
}
// prints 0,1,2,4,5,6continue jumps to the next iteration and break ends the loop immediately. Both work in for, while and switch.
if the an expression
// In collections (collection-if) var items = [ "always", if (active) "active", "end", ]; // Assignment with a ternary String r = active ? "yes" : "not";
Inside lists you can use if to include elements conditionally. For simple expressions, prefer the ternary operator ? :.
for-in
var fruits = ["apple", "pear", "grape"];
for (var fruit in fruits) {
print(fruit);
}
for (var i in [1, 2, 3]) {
print(i * 2);
}for-in iterates over each element of a collection without needing an index. It's the most readable way to iterate over a List, Set or Map.keys.
Ternary operator
int age = 20;
String r = age >= 18 ? "adult" : "smallest";
// Chained (avoid overusing)
String level = grade >= 18 ? "A"
: grade >= 14 ? "B"
: "C";The ternary condition ? valueIfTrue : valueIfFalse is a compact if-else in an expression. Avoid chaining too many to keep readability.
switch / case
switch (day) {
case 1:
print("Monday");
break;
case 2:
print("Tuesday");
break;
default:
print("Other");
}switch compares a value against several cases. Each case needs a break (or return) so it doesn't fall through to the next one.
while
int x = 5;
while (x > 0) {
print(x);
x--;
}
// x ends at 0while repeats the long the the condition is true. It checks the condition before each iteration, so it may never run the block.
Switch expression (Dart 3)
String name = switch (day) {
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday",
_ => "Other",
};
// With patterns
var type = switch (value) {
int() => "integer",
String() => "text",
_ => "unknown",
};In Dart 3 the switch can be an expression that returns a value. The _ is the default case and patterns allow advanced matching.
do-while
int x = 0;
do {
print(x);
x++;
} while (x < 5);
// runs at least oncedo-while runs the block before checking the condition, guaranteeing at least one execution. Useful for menus and validations.
Functions
Simple function
int add(int a, int b) {
return a + b;
}
void greeting(String name) {
print("Hello $name");
}
print(add(2, 3)); // 5Declare the return type before the name. void indicates it returns no value. Parameters can have explicit types for greater safety.
Default values
void connect({
String host = "localhost",
int port = 3306,
bool ssl = false,
}) {
print("$host:$port ssl=$ssl");
}
connect(port: 5432);Default values apply when the argument is not provided. In named parameters, optional ones are automatically nullable or have a default.
Generic functions
T first<T>(List<T> list) {
return list.first;
}
print(first<int>([1, 2, 3])); // 1
print(first<String>(["a", "b"])); // "a"<T> makes the function generic, working with any type. The type can be inferred or specified explicitly at the call.
Arrow function (=>)
int double(int x) => x * 2;
bool ePar(int n) => n % 2 == 0;
// Equivalent to:
int triple(int x) {
return x * 3;
}The => expressao syntax is a shortcut for single-expression functions that return that value. It replaces { return ...; }.
Anonymous functions (lambda)
var list = [1, 2, 3];
list.forEach((n) => print(n));
var double = (int x) => x * 2;
print(double(5)); // 10
var sum = (int a, int b) {
return a + b;
};Functions without a name can be assigned to variables or passed the arguments. They are the basis of callbacks and of methods like map() and where().
main function
void main() {
print("Start da app");
}
// With arguments (CLI)
void main(List<String> args) {
print(args);
}main() is the entry point of any Dart program. In Flutter apps it's where runApp() is called.
Named parameters
void hello({required String name, int age = 0}) {
print("$name tem $age");
}
hello(name: "Anna", age: 30);
hello(name: "Ray"); // age = 0Parameters inside { } are passed by name, in any order. required makes them mandatory and they can have default values.
Function the a parameter
int apply(int x, int Function(int) f) {
return f(x);
}
print(apply(5, (n) => n * 2)); // 10
print(apply(5, (n) => n + 1)); // 6Functions are first-class objects in Dart. The type int Function(int) describes a function that takes and returns an int.
required parameter
class User {
final String name;
final String email;
User({required this.name, required this.email});
}
var u = User(name: "Anna", email: "a@b.com");required forces you to pass the named argument, avoiding unexpected nulls. It's essential in widget and model constructors.
Optional positional parameters
void log(String msg, [int level = 0]) {
print("[$level] $msg");
}
log("error", 2); // [2] error
log("info"); // [0] infoParameters inside [ ] are positional and optional. They must come after the required ones and can have a default value.
Function the a return value
Function multiplier(int factor) {
return (int x) => x * factor;
}
var double = multiplier(2);
var triple = multiplier(3);
print(double(5)); // 10
print(triple(5)); // 15A function can return another function, creating closures that capture variables from the outer scope (here factor).
Variable scope
String global = "outside";
void test() {
String local = "inside";
print(global); // accessible
print(local);
}
// print(local); // ERROR: out of scopeVariables declared inside a function are local and inaccessible outside it. Dart has lexical scope: { } blocks create new scopes.
Classes e OOP
Basic class
class Person {
String name;
int age;
Person(this.name, this.age);
}
var p = Person("Anna", 30);
print(p.name); // AnnaThe constructor Person(this.name, this.age) automatically assigns the arguments to the fields. It's Dart's idiomatic and compact form.
Abstract class
abstract class Shape {
double area();
}
class Circle extends Shape {
final double radius;
Circle(this.radius);
@override
double area() => 3.14 * radius * radius;
}An abstract class cannot be instantiated and defines a contract. Subclasses are required to implement the abstract methods.
Advanced enum (Dart 2.17+)
enum Planet {
earth(9.8),
mars(3.7),
moon(1.6);
final double gravity;
const Planet(this.gravity);
}
print(Planet.earth.gravity); // 9.8Enums in Dart can have fields, constructors and methods. Each value passes arguments to the const constructor, allowing associated data.
super parameters (Dart 2.17+)
class Animal {
final String name;
Animal(this.name);
}
class Dog extends Animal {
final String raca;
Dog(super.name, this.raca);
}
var c = Dog("Rex", "Labrador");super.name in the constructor automatically forwards the argument to the superclass, avoiding writing : super(name).
Named constructor
class Point {
final int x, y;
const Point(this.x, this.y);
Point.origin() : this(0, 0);
Point.eixoX(this.x) : y = 0;
}
var p = Point.origin();Named constructors like Point.origin() offer alternative ways to create objects. The : this(...) delegates to another constructor.
Interface (implements)
class Point {
int x = 0, y = 0;
}
class Ponto3D implements Point {
int x = 0, y = 0, z = 0;
}
void print(Point p) => print(p.x);In Dart any class can be an interface with implements. The class must reimplement all the members of the interface.
Extension methods
extension StringExtra on String {
String get invertida => split("").reversed.join();
bool get eNumero => int.tryParse(this) != null;
}
print("abc".invertida); // "cba"
print("123".eNumero); // trueAn extension adds methods to existing types without modifying them. They are accessed the if they were the type's native methods.
Static members
class Mathematics {
static const double pi = 3.14159;
static double areaCirculo(double r) {
return pi * r * r;
}
}
print(Mathematics.pi);
print(Mathematics.areaCirculo(2));static members belong to the class, not the instance. They are accessed by the class name, without creating an object. Useful for utilities and constants.
Getters and Setters
class Rectangle {
double width = 0, height = 0;
double get area => width * height;
set area(double v) {
width = height = v;
}
}
var r = Rectangle();
r.area = 5; // calls the setter
print(r.area); // calls the getterget creates a read-only computed property and set allows assignment with logic. They are accessed like normal fields, without parentheses.
Mixin (with)
mixin Swimmer {
void swim() => print("swimming");
}
mixin Flyer {
void fly() => print("flying");
}
class Duck extends Animal with Swimmer, Flyer {}
Duck().swim();
Duck().fly();A mixin adds behavior to several classes without classic inheritance. It's applied with with and can combine several mixins.
Generic classes
class Box<T> {
T value;
Box(this.value);
}
var c = Box<int>(42);
var s = Box<String>("hello");
print(c.value); // 42<T> makes the class reusable for any type. The type is fixed at instantiation, ensuring type safety.
Records (Dart 3)
(String, int) person = ("Anna", 30);
print(person.$1); // Anna
print(person.$2); // 30
// With names
({String name, int age}) p = (name: "Anna", age: 30);
print(p.name);records group several values without creating a class. They can be positional ($1, $2) or named.
Inheritance (extends)
class Animal {
void speak() => print("...");
}
class Dog extends Animal {
@override
void speak() => print("Au au");
}
Dog().speak(); // Au auextends inherits fields and methods from the superclass. @override indicates you're overriding an inherited method.
Simple enum
enum State { active, inactive, pending }
State e = State.active;
print(e.name); // "active"
print(e.index); // 0
if (e == State.active) {
print("active!");
}enum defines a fixed set of values. .name returns the name the a string and .index the position (starts at 0).
super operator
class Animal {
final String name;
Animal(this.name);
}
class Dog extends Animal {
final String raca;
Dog(String name, this.raca) : super(name);
}
var c = Dog("Rex", "Labrador");super(...) calls the superclass constructor. In Dart 2.17+ you can use super.name directly in the parameters.
Factory constructor
class Logger {
static final Logger _inst = Logger._();
Logger._();
factory Logger() => _inst;
void log(String msg) => print(msg);
}
var a = Logger();
var b = Logger(); // same instanceA factory constructor controls object creation, and can return existing instances (singleton) or instances of subclasses.
Collections
List
var list = [1, 2, 3]; list.add(4); // adds list.remove(1); // removes the value 1 list.removeAt(0); // removes by index list.length; // size list[0]; // access by index list.first; list.last;
A List is an ordered, dynamic collection. add() inserts at the end, remove() deletes by value and the index accesses the elements.
any() and every()
var list = [1, 2, 3, 4]; list.any((e) => e > 3); // true (any) list.every((e) => e > 0); // true (all) list.contains(2); // true list.isEmpty; // false
any() checks whether at least one element meets the condition and every() whether all of them do.
Collection-if and for
bool active = true; var items = [ "base", if (active) "extra", for (var i in [1, 2]) "item$i", ]; // ["base", "extra", "item1", "item2"]
Inside collection literals you can use if and for to build lists dynamically. Widely used in a widget's children.
Map
var map = {"a": 1, "b": 2};
map["c"] = 3; // adds/updates
map.remove("a"); // removes key
map.keys; // ("b", "c")
map.values; // (2, 3)
map.containsKey("b"); // true
map["x"] ??= 0; // only if it doesn't existThe Map stores key-value pairs. Accessing a non-existent key returns null. keys and values return iterables.
fold() and reduce()
var list = [1, 2, 3, 4]; int sum = list.fold(0, (acc, e) => acc + e); // 10 int product = list.reduce((acc, e) => acc * e); // 24
fold() accumulates a value starting from an initial one. reduce() does the same but uses the first element the the initial value.
List.generate()
var squares = List.generate(5, (i) => i * i); // [0, 1, 4, 9, 16] var even = List.generate(10, (i) => i * 2); // [0, 2, 4, ..., 18]
List.generate() creates a list from a function that receives the index. Ideal for generating sequences or test data.
Set
var conjunto = {1, 2, 3};
conjunto.add(3); // ignored (duplicate)
conjunto.add(4);
conjunto.contains(1); // true
conjunto.length; // 4
var uniao = {1, 2}.union({2, 3}); // {1,2,3}The Set stores unique values with no guaranteed order. Adding a duplicate has no effect. Useful for removing repetitions.
sort() and reversed
var list = [3, 1, 4, 1, 5]; list.sort(); // [1, 1, 3, 4, 5] list.sort((a, b) => b.compareTo(a)); // desc var names = ["ana", "rui"]; names.reversed.toList(); // ["rui", "ana"]
sort() sorts the list in place (mutates the original). Pass a comparator for custom ordering. reversed returns the reverse order.
forEach and indexed
var fruits = ["apple", "pear"];
fruits.forEach((f) => print(f));
// With index (Dart 3)
for (var (i, f) in fruits.indexed) {
print("$i: $f");
}forEach() runs a function for each element. .indexed provides (index, value) pairs to iterate with position.
map() and where()
var list = [1, 2, 3, 4, 5]; var double = list.map((e) => e * 2).toList(); // [2, 4, 6, 8, 10] var even = list.where((e) => e % 2 == 0).toList(); // [2, 4]
map() transforms each element and where() filters. Both return an Iterable; use toList() to materialize it.
Spread operator (...)
var a = [1, 2];
var b = [0, ...a, 3]; // [0, 1, 2, 3]
List<int>? nula;
var c = [...?nula, 1]; // null-safe spread
var map = {...outroMapa, "x": 1};... expands the elements of one collection inside another. ...? avoids an error if the collection is null.
Map.fromEntries and transformations
var map = {"a": 1, "b": 2};
var dobrado = map.map((k, v) => MapEntry(k, v * 2));
// {"a": 2, "b": 4}
var list = map.entries.toList();A Map's map() returns a MapEntry to transform keys and values. entries exposes the pairs the an iterable.
Async e Streams
async / await
Future<String> fetch() async {
var r = await http.get(url);
return r.body;
}
void main() async {
var data = await fetch();
print(data);
}async marks an asynchronous function and await pauses execution until the Future completes, keeping the code readable.
Future.wait (parallel)
var results = await Future.wait([ buscarA(), buscarB(), buscarC(), ]); print(results); // [a, b, c]
Future.wait() runs several Futures in parallel and waits for all of them. Faster than sequential await when they are independent.
onError and catchError
// With catchError
fetch().catchError((e) {
print("Failed: $e");
return "fallback";
});
// Stream with onError
stream.listen(
(d) => print(d),
onError: (e) => print(e),
);catchError() handles errors from a Future and can return an alternative value. In listen() use the onError parameter.
Future
Future<String> task() {
return Future.delayed(
Duration(seconds: 2),
() => "ready",
);
}
task().then((v) => print(v));A Future<T> represents a value available in the future. .then() registers a callback run when the result is ready.
StreamController
final controller = StreamController<int>(); controller.stream.listen((n) => print(n)); controller.add(1); controller.add(2); await controller.close();
The StreamController lets you emit values manually to a stream via .add(). .listen() subscribes to the events.
async* and yield*
Stream<int> upTo(int n) async* {
for (var i = 0; i <= n; i++) {
yield i;
}
}
Stream<int> double() async* {
yield* upTo(3).map((e) => e * 2);
}async* returns a Stream and yield emits values. yield* re-emits all the values of another stream.
try / catch async
try {
var data = await fetch();
print(data);
} catch (e) {
print("Error: $e");
} finally {
print("terminado");
}Errors in await code are caught with a normal try/catch. finally always runs, with or without an error.
StreamBuilder (widget)
StreamBuilder<int>(
stream: count(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text("${snapshot.data}");
}
return CircularProgressIndicator();
},
)The StreamBuilder rebuilds the UI whenever the stream emits a value. The snapshot holds the current state and data.
sync* and generators
Iterable<int> fib(int n) sync* {
int a = 0, b = 1;
for (var i = 0; i < n; i++) {
yield a;
[a, b] = [b, a + b];
}
}
print(fib(6).toList()); // [0,1,1,2,3,5]sync* creates a synchronous generator that returns an Iterable. The values are produced lazily the you iterate.
Stream
Stream<int> count() async* {
for (var i = 0; i < 5; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
await for (var n in count()) {
print(n);
}A Stream emits several values over time. async* with yield produces the values and await for consumes them.
FutureBuilder (widget)
FutureBuilder<String>(
future: fetch(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
return Text(snapshot.data ?? "");
}
return CircularProgressIndicator();
},
)The FutureBuilder shows a loading state while the Future hasn't completed and the final UI when it finishes. Ideal for HTTP requests.
Timeout and retry
try {
var r = await fetch()
.timeout(Duration(seconds: 5));
} on TimeoutException {
print("Took too long");
}
// Retry on streams
stream.timeout(Duration(seconds: 3));timeout() throws a TimeoutException if the Future doesn't complete in time. Essential for robust network requests.
Widgets e Layout
StatelessWidget
class Hello extends StatelessWidget {
const Hello({super.key});
@override
Widget build(BuildContext context) {
return Text("Hello World");
}
}A StatelessWidget is immutable: it describes the UI from its parameters. The build() method is called whenever the widget needs to be redrawn.
ListView.builder
ListView.builder(
itemCount: items.length,
itemBuilder: (context, i) {
return ListTile(
title: Text(items[i]),
);
},
)The ListView.builder creates items lazily, building only the visible ones. Essential for large lists with good performance.
Icons
Icon(Icons.favorite, color: Colors.red, size: 32)
Icon(Icons.add)
IconButton(
icon: Icon(Icons.delete),
onPressed: () {},
)The Icon widget shows Material icons from Icons. The IconButton makes the icon clickable with an onPressed callback.
Card and ListTile
Card(
elevation: 4,
child: ListTile(
leading: Icon(Icons.person),
title: Text("Anna Silva"),
subtitle: Text("Programmer"),
trailing: Icon(Icons.arrow_forward),
onTap: () {},
),
)The Card is a Material surface with a shadow. The ListTile is a standard row with leading, title, subtitle and trailing.
Spacer and Divider
Row(
children: [
Text("Left"),
Spacer(), // pushes to the sides
Text("Right"),
],
)
Divider(thickness: 1, color: Colors.grey)The Spacer takes up all the free space in a Row/Column. The Divider draws a horizontal separator line.
Scaffold
Scaffold(
appBar: AppBar(title: Text("Title")),
body: Center(child: Text("Content")),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
)The Scaffold is the base structure of a Material screen. It defines appBar, body, floatingActionButton, drawer and bottomNavigationBar.
GestureDetector
GestureDetector(
onTap: () => print("toque"),
onDoubleTap: () => print("duplo"),
onLongPress: () => print("long"),
child: Text("Toca here"),
)The GestureDetector detects gestures like onTap, onDoubleTap and onLongPress on any child widget.
Stack and Positioned
Stack(
children: [
Image.asset("assets/background.png"),
Positioned(
bottom: 10,
right: 10,
child: Text("Legenda"),
),
],
)The Stack overlaps widgets in layers. The Positioned places a child with absolute coordinates inside the stack.
GridView
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: items.length,
itemBuilder: (context, i) => Card(),
)The GridView.builder creates a scrollable grid. crossAxisCount defines the number of columns and the spacing values the gaps.
SafeArea
SafeArea(
child: Column(
children: [
Text("Content safe"),
],
),
)The SafeArea adds padding to avoid the notch, status bar and system buttons. It ensures the content stays within the visible area.
Column and Row
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("A"),
Text("B"),
],
)A Column arranges its children vertically and a Row horizontally. mainAxisAlignment aligns along the main axis and crossAxisAlignment along the cross axis.
Text and TextStyle
Text(
"Hello Flutter",
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
textAlign: TextAlign.center,
)Text displays text and TextStyle controls font, size, weight and color. textAlign sets the horizontal alignment.
Expanded and Flexible
Row(
children: [
Expanded(flex: 2, child: Text("A")),
Expanded(flex: 1, child: Text("B")),
],
)The Expanded makes the child fill the available space in a Row or Column. flex defines the ratio between several expanded widgets.
Wrap
Wrap(
spacing: 8,
runSpacing: 8,
children: [
Chip(label: Text("Dart")),
Chip(label: Text("Flutter")),
Chip(label: Text("Firebase")),
],
)The Wrap lays children out in a line and breaks to the next line when they don't fit. Ideal for tags and chips with variable width.
Container
Container(
width: 100,
height: 100,
padding: EdgeInsets.all(8),
margin: EdgeInsets.all(16),
color: Colors.blue,
child: Text("Hello"),
)The Container is a versatile box with dimensions, padding, margin, color and decoration. It combines several layout widgets into one.
Images
Image.asset("assets/photo.png", width: 200)
Image.network("https://site.com/img.jpg")
Image.file(File("/path/photo.png"))
CircleAvatar(
backgroundImage: AssetImage("assets/profile.png"),
)Image.asset loads from local assets, Image.network from a URL and Image.file from the device. Register the assets in pubspec.yaml.
Padding and SizedBox
Padding(
padding: EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
child: Text("Hello"),
)
SizedBox(height: 20) // spacing
SizedBox(width: 100, child: button)The Padding adds inner space with EdgeInsets. The SizedBox creates fixed spacing or forces dimensions on a child.
SingleChildScrollView
SingleChildScrollView(
child: Column(
children: [
Text("A"),
Text("B"),
Text("C"),
],
),
)The SingleChildScrollView adds scrolling to a single child. Used when the content may exceed the screen and to avoid overflow.
Status and Navigation
StatefulWidget
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _ContadorState();
}
class _ContadorState extends State<Counter> {
int n = 0;
@override
Widget build(BuildContext context) {
return Text("$n");
}
}A StatefulWidget has mutable state stored in a State class. createState() links the widget to its persistent state.
Navigation (push)
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => DetalhePage(),
),
);Navigator.push() pushes a new screen onto the stack. MaterialPageRoute defines the transition and the builder creates the destination page.
showDialog
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text("Attention"),
content: Text("Confirm the action?"),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text("OK"),
),
],
),
);showDialog() displays a modal AlertDialog. The actions contain buttons and Navigator.pop() closes the box.
InheritedWidget / Theme
// Access the theme in any widget Theme.of(context).primaryColor; MediaQuery.of(context).size; // Shared data via InheritedWidget var data = Dependency.of(context);
The InheritedWidget shares data down the widget tree. Theme.of() and MediaQuery.of() are classic examples of this pattern.
setState
ElevatedButton(
onPressed: () {
setState(() {
n++;
});
},
child: Text("Increment"),
)setState() updates the state and marks the widget for rebuilding. It should only be called inside a State class.
Navigation (pop)
Navigator.pop(context); // Return a result Navigator.pop(context, "value"); // Receive it on push var r = await Navigator.push(context, route);
Navigator.pop() closes the current screen. It can return a result that is received by the await of the push() that opened it.
showModalBottomSheet
showModalBottomSheet(
context: context,
builder: (_) => Container(
height: 200,
child: Center(child: Text("Options")),
),
);showModalBottomSheet() opens a panel from the bottom of the screen. Widely used for action menus and selections.
ValueNotifier and ValueListenableBuilder
final counter = ValueNotifier<int>(0);
ValueListenableBuilder<int>(
valueListenable: counter,
builder: (context, value, _) {
return Text("$value");
},
)
counter.value++; // updates the UIThe ValueNotifier holds a notifiable value. The ValueListenableBuilder rebuilds only the affected part when .value changes, without setState.
Lifecycle (State)
class _PaginaState extends State<Page> {
@override
void initState() {
super.initState(); // 1st: initialization
}
@override
void dispose() {
super.dispose(); // last: cleanup
}
}initState() runs once on creation (ideal for subscriptions) and dispose() on destruction (frees resources and controllers).
Named routes
MaterialApp(
initialRoute: "/",
routes: {
"/": (_) => Home(),
"/detail": (_) => Detail(),
},
)
Navigator.pushNamed(context, "/detail");Define routes in the MaterialApp's routes and navigate by name with pushNamed(). Cleaner for apps with many screens.
SnackBar
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Saved!"),
duration: Duration(seconds: 2),
action: SnackBarAction(
label: "Undo",
onPressed: () {},
),
),
);The SnackBar shows a temporary message at the bottom. It's displayed via ScaffoldMessenger and can have a SnackBarAction.
didUpdateWidget
@override
void didUpdateWidget(covariant MeuWidget old) {
super.didUpdateWidget(old);
if (old.id != widget.id) {
reload();
}
}didUpdateWidget() is called when the parent widget changes and the State is reused. Compare the old widget with the new one via widget.
Passing arguments
// Send
Navigator.pushNamed(
context, "/detail",
arguments: {"id": 42},
);
// Receive
final args = ModalRoute.of(context)!
.settings.arguments the Map;The pushNamed()'s arguments sends data to the route. On the destination page you get it via ModalRoute.of(context).settings.arguments.
pushReplacement
Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => Login()), ); // Clear the whole stack Navigator.pushAndRemoveUntil( context, route, (r) => false, );
pushReplacement() replaces the current screen (with no going back). pushAndRemoveUntil() pushes and removes previous screens based on a predicate.
CLI, Themes and Best Practices
Create and run a project
flutter create my_app cd my_app flutter run # Run on a specific device flutter run -d chrome flutter devices
flutter create generates the project structure and flutter run compiles and runs it with hot reload. -d chooses the device.
Theme and colors
MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.light,
),
)
// Access it in any widget
Theme.of(context).primaryColor;
Color(0xFF6200EE);
Colors.blue.shade700;The ThemeData defines global colors and styles. Theme.of(context) accesses the theme. Colors use Colors or hexadecimal Color(0xFF...).
Folder structure
lib/ main.dart models/ # data classes services/ # API, logic screens/ # pages widgets/ # reusable widgets theme/ # styles
Organize the code into models, services, screens and widgets. Separate the UI from the business logic for maintainability.
Useful CLI commands
flutter pub get # installs dependencies flutter pub add http # adds a package flutter clean # cleans the build flutter build apk # generates an APK flutter build appbundle flutter doctor # checks the environment
flutter pub manages dependencies. flutter clean fixes cache problems and flutter doctor diagnoses the installation.
MediaQuery (dimensions)
final size = MediaQuery.of(context).size;
double width = size.width;
double height = size.height;
double padding = MediaQuery.of(context)
.padding.top; // status barThe MediaQuery provides screen dimensions and device information. Essential for responsive layouts and adapting the UI.
Best practices
// 1. Small, reusable widgets // 2. const whenever possible // 3. Separate UI from logic // 4. Avoid excessive setState // 5. Free controllers in dispose()
Prefer small widgets, use const, separate the logic from the UI and free resources in dispose(). Avoid unnecessary rebuilds.
pubspec.yaml
dependencies:
flutter:
sdk: flutter
http: ^1.2.0
provider: ^6.1.0
flutter:
assets:
- assets/
- assets/images/The pubspec.yaml declares dependencies and assets. After changing it, run flutter pub get. The ^ accepts compatible updates.
LayoutBuilder (responsive)
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
return Text("Screen wide");
}
return Text("Screen narrow");
},
)The LayoutBuilder gives access to the parent's constraints, letting you adapt the layout to the available space (responsiveness).
Debug and print
print("value: $x");
debugPrint("text long...");
// Flutter DevTools (widget inspector)
flutter run
# opens DevTools in the terminal
assert(x > 0, "x must be positive");Use print() for simple logs and debugPrint() for long texts. Flutter DevTools inspects widgets, performance and network.
Hot reload and restart
# During flutter run: r # hot reload (fast, keeps state) R # hot restart (resets state) q # quit o # switch platform
hot reload (r) applies UI changes while keeping the state. hot restart (R) resets the app's state completely.
const (performance)
// Constant widget (doesn't rebuild)
const Text("Hello")
const SizedBox(height: 10)
const Icon(Icons.star)
// const constructor
class Title extends StatelessWidget {
const Title({super.key});
}Mark immutable widgets with const so Flutter reuses them and avoids unnecessary rebuilds, improving performance.
Assets and fonts
# pubspec.yaml
flutter:
assets:
- assets/logo.png
fonts:
- family: Roboto
fonts:
- asset: fonts/Roboto.ttf
// Usage
Image.asset("assets/logo.png")
TextStyle(fontFamily: "Roboto")Register assets and fonts in the pubspec.yaml to include them in the build. Access them with Image.asset() and fontFamily in the TextStyle.
Forms and Input
Basic TextField
TextField(
decoration: InputDecoration(
labelText: "Name",
hintText: "Enter your name",
border: OutlineInputBorder(),
),
onChanged: (text) => print(text),
)The TextField receives text from the user. The InputDecoration defines label, hint and border. onChanged fires on every change.
Keyboard and focus
TextField( keyboardType: TextInputType.number, obscureText: true, // password textInputAction: TextInputAction.next, autofocus: true, ) // Hide the keyboard FocusScope.of(context).unfocus();
The keyboardType chooses the keyboard, obscureText hides characters (password) and textInputAction defines the action button. unfocus() closes the keyboard.
DropdownButton
String? country;
DropdownButton<String>(
value: country,
hint: Text("Escolhe one country"),
items: ["Portugal", "Brasil"]
.map((p) => DropdownMenuItem(
value: p,
child: Text(p),
))
.toList(),
onChanged: (v) => setState(() => country = v),
)The DropdownButton shows a dropdown list. The items are DropdownMenuItems and onChanged receives the selected value.
TextEditingController
final controller = TextEditingController();
TextField(controller: controller);
// Read and set the value
print(controller.text);
controller.text = "new value";
controller.clear();
@override
void dispose() {
controller.dispose();
super.dispose();
}The TextEditingController lets you read and control the text programmatically. It must be freed in dispose() to avoid memory leaks.
Checkbox
bool aceite = false;
CheckboxListTile(
title: Text("I accept the terms"),
value: aceite,
onChanged: (v) {
setState(() => aceite = v ?? false);
},
)The Checkbox toggles a bool. The CheckboxListTile joins the box with a text. Update the state in onChanged.
Slider
double volume = 0.5; Slider( value: volume, min: 0, max: 1, divisions: 10, label: volume.toStringAsFixed(1), onChanged: (v) => setState(() => volume = v), )
The Slider selects a value in a range. min/max define the limits, divisions the steps and label the displayed value.
Form and validation
final _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
child: Column(children: [
TextFormField(
validator: (v) =>
v!.isEmpty ? "Required" : null,
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// valid
}
},
child: Text("Send"),
),
]),
)The Form groups fields with a GlobalKey<FormState>. The validator returns an error message or null if valid. validate() checks them all.
Radio
String option = "a";
RadioListTile(
title: Text("Option A"),
value: "a",
groupValue: option,
onChanged: (v) => setState(() => option = v!),
)The Radio selects one option in a group. The groupValue is the current value and value the one of this button. Only one stays active per group.
DatePicker
final data = await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2100), ); if (data != null) print(data);
showDatePicker() opens a date picker and returns a DateTime?. Set initialDate, firstDate and lastDate.
TextFormField
TextFormField(
decoration: InputDecoration(labelText: "Email"),
keyboardType: TextInputType.emailAddress,
validator: (v) {
if (v == null || !v.contains("@")) {
return "Email invalid";
}
return null;
},
)The TextFormField is a TextField integrated into a Form, with a validator. The keyboardType adjusts the keyboard (email, number, etc.).
Switch
bool active = true;
SwitchListTile(
title: Text("Notifications"),
value: active,
onChanged: (v) {
setState(() => active = v);
},
)The Switch is an on/off toggle. The SwitchListTile combines it with a label. Update the bool in onChanged.
FocusNode
final foco = FocusNode();
TextField(focusNode: foco);
// Give focus programmatically
foco.requestFocus();
@override
void dispose() {
foco.dispose();
super.dispose();
}The FocusNode controls a field's focus. requestFocus() activates it programmatically. Free it in dispose().