DevTools

Cheatsheet Flutter

Framework para desenvolvimento mobile

Back to languages
Flutter
132 cards found
Categories:
Versions:

Basic Syntax (Dart)


14 cards
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"); // replace

Strings 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};  // set

Dart 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


10 cards
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,6

continue 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 0

while 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 once

do-while runs the block before checking the condition, guaranteeing at least one execution. Useful for menus and validations.

Functions


12 cards
Simple function
int add(int a, int b) {
  return a + b;
}

void greeting(String name) {
  print("Hello $name");
}

print(add(2, 3)); // 5

Declare 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 = 0

Parameters 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)); // 6

Functions 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] info

Parameters 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)); // 15

A 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 scope

Variables declared inside a function are local and inaccessible outside it. Dart has lexical scope: { } blocks create new scopes.

Classes e OOP


16 cards
Basic class
class Person {
  String name;
  int age;

  Person(this.name, this.age);
}

var p = Person("Anna", 30);
print(p.name); // Anna

The 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.8

Enums 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);   // true

An 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 getter

get 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 au

extends 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 instance

A factory constructor controls object creation, and can return existing instances (singleton) or instances of subclasses.

Collections


12 cards
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 exist

The 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


12 cards
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


18 cards
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


14 cards
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 UI

The 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


12 cards
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 bar

The 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


12 cards
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().