Flutter: Criando seu primeiro App Mobile completo
Guia completo Flutter: instalação, widgets, navegação, state management com Provider, integração com APIs e publicação nas lojas.
O que é Flutter e por que usar
Flutter é um framework open-source do Google para criar apps nativos para iOS, Android, Web e Desktop com uma única base de código. Usa a linguagem Dart e renderiza com o próprio motor gráfico (Skia), garantindo performance e UI consistente.
Vantagens do Flutter
- Hot Reload: veja mudanças em tempo real sem perder estado
- UI declarativa: widgets compostos como árvore
- Performance nativa: compila para ARM/x86 nativo
- Uma codebase: iOS, Android, Web, Desktop
- Ecosistema maduro: pacotes no pub.dev
Passo 1: Instalação e setup
Instalar Flutter SDK
macOS/Linux
# Baixe o Flutter SDK
git clone https://github.com/flutter/flutter.git -b stable
# Adicione ao PATH (no ~/.bashrc ou ~/.zshrc)
export PATH="$PATH:$HOME/flutter/bin"
# Verifique instalação
flutter doctor
Windows
Baixe o ZIP do site oficial (flutter.dev) e adicione ao PATH.
Instalar dependências
# Android Studio (para emulador Android)
# Xcode (macOS, para emulador iOS)
# VS Code com extensão Flutter
# Verificar dependências
flutter doctor
# Aceitar licenças Android
flutter doctor --android-licenses
Criar primeiro projeto
flutter create meu_app
cd meu_app
flutter run
Passo 2: Estrutura de um projeto Flutter
meu_app/
├── android/ # Código nativo Android
├── ios/ # Código nativo iOS
├── lib/ # Código Dart (seu app)
│ └── main.dart # Ponto de entrada
├── test/ # Testes
├── pubspec.yaml # Dependências (como package.json)
Passo 3: Widgets básicos
Tudo no Flutter é widget. Widgets são elementos da UI que se compõem em árvore.
Stateless vs Stateful Widgets
StatelessWidget (imutável)
import 'package:flutter/material.dart';
class MeuWidget extends StatelessWidget {
final String texto;
const MeuWidget({Key? key, required this.texto}) : super(key: key);
@override
Widget build(BuildContext context) {
return Text(texto);
}
}
StatefulWidget (mutável, tem estado)
class Contador extends StatefulWidget {
@override
_ContadorState createState() => _ContadorState();
}
class _ContadorState extends State<Contador> {
int _contador = 0;
void _incrementar() {
setState(() {
_contador++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Contador: $_contador'),
ElevatedButton(
onPressed: _incrementar,
child: Text('Incrementar'),
),
],
);
}
}
Widgets comuns
Layout
// Container (box model)
Container(
width: 200,
height: 100,
padding: EdgeInsets.all(16),
margin: EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
),
child: Text('Hello'),
)
// Row (horizontal)
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Icon(Icons.star),
Text('5.0'),
Text('(120)'),
],
)
// Column (vertical)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Título'),
Text('Subtítulo'),
],
)
// ListView (scroll vertical)
ListView(
children: [
ListTile(title: Text('Item 1')),
ListTile(title: Text('Item 2')),
ListTile(title: Text('Item 3')),
],
)
// GridView (grid responsivo)
GridView.count(
crossAxisCount: 2,
children: List.generate(10, (index) {
return Card(
child: Center(child: Text('Item $index')),
);
}),
)
Input
// TextField
TextField(
decoration: InputDecoration(
labelText: 'Email',
hintText: 'seu@email.com',
prefixIcon: Icon(Icons.email),
),
onChanged: (value) => print(value),
)
// ElevatedButton
ElevatedButton(
onPressed: () => print('Clicou'),
child: Text('Enviar'),
)
// Checkbox
Checkbox(
value: true,
onChanged: (value) => print(value),
)
Passo 4: Exemplo prático - Lista de Tarefas
import 'package:flutter/material.dart';
void main() {
runApp(MeuApp());
}
class MeuApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Lista de Tarefas',
theme: ThemeData(primarySwatch: Colors.blue),
home: ListaTarefas(),
);
}
}
class ListaTarefas extends StatefulWidget {
@override
_ListaTarefasState createState() => _ListaTarefasState();
}
class _ListaTarefasState extends State<ListaTarefas> {
final List<String> _tarefas = [];
final TextEditingController _controller = TextEditingController();
void _adicionarTarefa() {
if (_controller.text.isNotEmpty) {
setState(() {
_tarefas.add(_controller.text);
_controller.clear();
});
}
}
void _removerTarefa(int index) {
setState(() {
_tarefas.removeAt(index);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Minhas Tarefas'),
),
body: Column(
children: [
Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: InputDecoration(
hintText: 'Nova tarefa',
border: OutlineInputBorder(),
),
),
),
SizedBox(width: 8),
ElevatedButton(
onPressed: _adicionarTarefa,
child: Text('Adicionar'),
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: _tarefas.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_tarefas[index]),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => _removerTarefa(index),
),
);
},
),
),
],
),
);
}
}
Passo 5: Navegação entre telas
Navigator.push (ir para outra tela)
// Tela 1
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SegundaTela()),
);
},
child: Text('Ir para Tela 2'),
)
// Tela 2
class SegundaTela extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Tela 2')),
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.pop(context),
child: Text('Voltar'),
),
),
);
}
}
Named routes (rotas nomeadas)
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/detalhes': (context) => DetalhesScreen(),
'/perfil': (context) => PerfilScreen(),
},
)
// Navegar
Navigator.pushNamed(context, '/detalhes');
// Passar argumentos
Navigator.pushNamed(
context,
'/detalhes',
arguments: {'id': 123},
);
// Receber argumentos
final args = ModalRoute.of(context)!.settings.arguments as Map;
Passo 6: State Management com Provider
Instalar Provider
Adicione no pubspec.yaml:
dependencies:
flutter:
sdk: flutter
provider: ^6.1.1
Execute:
flutter pub get
Criar modelo de dados
import 'package:flutter/foundation.dart';
class ContadorModel extends ChangeNotifier {
int _contador = 0;
int get contador => _contador;
void incrementar() {
_contador++;
notifyListeners();
}
void decrementar() {
_contador--;
notifyListeners();
}
}
Configurar Provider
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => ContadorModel(),
child: MeuApp(),
),
);
}
Consumir estado
class TelaPrincipal extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Provider Demo')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Contador:'),
Consumer<ContadorModel>(
builder: (context, contador, child) {
return Text(
'${contador.contador}',
style: TextStyle(fontSize: 48),
);
},
),
],
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () {
context.read<ContadorModel>().incrementar();
},
child: Icon(Icons.add),
),
SizedBox(height: 8),
FloatingActionButton(
onPressed: () {
context.read<ContadorModel>().decrementar();
},
child: Icon(Icons.remove),
),
],
),
);
}
}
Passo 7: Integração com API REST
Instalar pacote HTTP
dependencies:
http: ^1.1.0
Buscar dados de uma API
import 'dart:convert';
import 'package:http/http.dart' as http;
class Usuario {
final int id;
final String nome;
final String email;
Usuario({required this.id, required this.nome, required this.email});
factory Usuario.fromJson(Map<String, dynamic> json) {
return Usuario(
id: json['id'],
nome: json['name'],
email: json['email'],
);
}
}
Future<List<Usuario>> buscarUsuarios() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/users'),
);
if (response.statusCode == 200) {
List<dynamic> body = jsonDecode(response.body);
return body.map((json) => Usuario.fromJson(json)).toList();
} else {
throw Exception('Erro ao carregar usuários');
}
}
Exibir dados com FutureBuilder
class ListaUsuarios extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Usuarios')),
body: FutureBuilder<List<Usuario>>(
future: buscarUsuarios(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Erro: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('Nenhum usuário encontrado'));
}
final usuarios = snapshot.data!;
return ListView.builder(
itemCount: usuarios.length,
itemBuilder: (context, index) {
final usuario = usuarios[index];
return ListTile(
leading: CircleAvatar(child: Text(usuario.nome[0])),
title: Text(usuario.nome),
subtitle: Text(usuario.email),
);
},
);
},
),
);
}
}
Passo 8: Persistência local (SQLite)
Instalar sqflite
dependencies:
sqflite: ^2.3.0
path: ^1.8.3
Criar database helper
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DatabaseHelper {
static final DatabaseHelper instance = DatabaseHelper._init();
static Database? _database;
DatabaseHelper._init();
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDB('tarefas.db');
return _database!;
}
Future<Database> _initDB(String filePath) async {
final dbPath = await getDatabasesPath();
final path = join(dbPath, filePath);
return await openDatabase(
path,
version: 1,
onCreate: _createDB,
);
}
Future _createDB(Database db, int version) async {
await db.execute('''
CREATE TABLE tarefas(
id INTEGER PRIMARY KEY AUTOINCREMENT,
titulo TEXT NOT NULL,
concluida INTEGER NOT NULL
)
''');
}
Future<int> inserir(Map<String, dynamic> row) async {
final db = await database;
return await db.insert('tarefas', row);
}
Future<List<Map<String, dynamic>>> buscarTodas() async {
final db = await database;
return await db.query('tarefas');
}
Future<int> atualizar(Map<String, dynamic> row) async {
final db = await database;
return await db.update('tarefas', row, where: 'id = ?', whereArgs: [row['id']]);
}
Future<int> deletar(int id) async {
final db = await database;
return await db.delete('tarefas', where: 'id = ?', whereArgs: [id]);
}
}
Passo 9: Publicar na Google Play e App Store
Android (Google Play)
- Crie uma conta de desenvolvedor ($25 uma vez)
- Configure signing key:
keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload - Configure
android/key.properties - Build release:
flutter build appbundle - Upload
build/app/outputs/bundle/release/app-release.aabno Play Console
iOS (App Store)
- Conta Apple Developer ($99/ano)
- Configure signing no Xcode
- Build release:
flutter build ipa - Upload via Xcode ou Transporter
- Submeta para review no App Store Connect
Boas práticas
- Use const widgets: melhora performance
- Evite setState() excessivo: use Provider ou Riverpod
- Separe widgets grandes: mantenha widgets pequenos e reutilizáveis
- Use keys quando necessário: para preservar estado em listas
- Teste no dispositivo real: emuladores não refletem performance real
Recursos úteis
- pub.dev: pacotes e plugins
- flutter.dev/docs: documentação oficial
- FlutterFlow: low-code para prototipar
- Widget of the Week: canal no YouTube do Flutter
Conclusão
Flutter permite criar apps nativos de alta qualidade com uma única codebase. Comece com widgets básicos, aprenda navegação e state management, integre APIs e publique nas lojas. Com hot reload e ecossistema rico, você pode iterar rapidamente e entregar apps profissionais para iOS e Android.
FAQ
Flutter é melhor que React Native?
Depende. Flutter tem melhor performance e UI consistente. React Native tem comunidade maior e usa JavaScript. Escolha baseado na sua stack.
Preciso saber Dart para usar Flutter?
Sim, mas Dart é simples se você conhece JavaScript, Java ou C#. A sintaxe é familiar e a curva de aprendizado é suave.
Flutter funciona para Web?
Sim, mas não é ideal para sites tradicionais. Use para web apps complexos ou para reusar código mobile.
Como debugar no Flutter?
Use DevTools, print(), debugger no VS Code, e Flutter Inspector para visualizar a árvore de widgets.
Flutter é gratuito?
Sim, totalmente open-source. Custos são apenas das contas de desenvolvedor (Google Play, App Store).