6 Maneiras de Gerenciar Estado em Aplicativos Flutter (Com Exemplos de Código)
Como gerenciar o estado no Flutter
Neste artigo, exploraremos seis formas populares de gerenciar estado no Flutter apps, incluindo exemplos reais e melhores práticas:
Gerenciamento de estado é um dos tópicos mais importantes - e debatidos - no desenvolvimento Flutter. Ele determina como seu app lida com mudanças nos dados e atualiza a interface do usuário de forma eficiente. O Flutter oferece várias estratégias para gerenciar estado — desde as mais simples até as altamente escaláveis.
As estratégias para gerenciamento de estado no Flutter são:
- 🧱
setState()
— A abordagem mais simples embutida - 🏗️ InheritedWidget — Fundamento do Flutter para propagação de estado
- 🪄 Provider — A solução recomendada para a maioria dos apps
- 🔒 Riverpod — Evolução moderna e segura de compilação do Provider
- 📦 Bloc — Para aplicações escaláveis e empresariais
- ⚡ GetX — Solução leve e completa
1. Usando setState()
— Os Fundamentos
A maneira mais simples de gerenciar estado no Flutter é usando o método embutido setState()
em um StatefulWidget
.
Essa abordagem é ideal para estado local da interface do usuário — onde o estado pertence a um widget e não precisa ser compartilhado por todo o app.
Exemplo: Aplicativo de Contador com setState()
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: CounterScreen());
}
}
class CounterScreen extends StatefulWidget {
@override
_CounterScreenState createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('setState Counter')),
body: Center(
child: Text('Count: $_count', style: TextStyle(fontSize: 24)),
),
floatingActionButton: FloatingActionButton(
onPressed: _increment,
child: Icon(Icons.add),
),
);
}
}
Vantagens
- Muito simples de implementar
- Ideal para estado local ou temporário da interface do usuário
- Nenhuma dependência externa
Desvantagens
- Não escala para aplicações grandes
- Difícil compartilhar estado entre widgets
- Lógica misturada com a interface do usuário
Use-o quando:
- Prototipando ou construindo pequenos widgets
- Lidando com estado isolado da interface do usuário (por exemplo, alternando um botão, mostrando um modal)
2. InheritedWidget — Fundamento do Flutter
InheritedWidget
é o mecanismo de baixo nível que o Flutter usa para propagar dados pela árvore de widgets. A maioria das soluções de gerenciamento de estado (incluindo Provider) são construídas sobre ele.
Entender InheritedWidget ajuda você a compreender como o gerenciamento de estado do Flutter funciona sob o capô.
Exemplo: Gerenciador de Tema com InheritedWidget
import 'package:flutter/material.dart';
// O InheritedWidget que armazena os dados do tema
class AppTheme extends InheritedWidget {
final bool isDarkMode;
final Function toggleTheme;
const AppTheme({
Key? key,
required this.isDarkMode,
required this.toggleTheme,
required Widget child,
}) : super(key: key, child: child);
// Método de acesso para obter o AppTheme mais próximo na árvore de widgets
static AppTheme? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<AppTheme>();
}
@override
bool updateShouldNotify(AppTheme oldWidget) {
return isDarkMode != oldWidget.isDarkMode;
}
}
// Wrapper stateful para gerenciar mudanças de estado
class ThemeManager extends StatefulWidget {
final Widget child;
const ThemeManager({Key? key, required this.child}) : super(key: key);
@override
_ThemeManagerState createState() => _ThemeManagerState();
}
class _ThemeManagerState extends State<ThemeManager> {
bool _isDarkMode = false;
void _toggleTheme() {
setState(() {
_isDarkMode = !_isDarkMode;
});
}
@override
Widget build(BuildContext context) {
return AppTheme(
isDarkMode: _isDarkMode,
toggleTheme: _toggleTheme,
child: widget.child,
);
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ThemeManager(
child: Builder(
builder: (context) {
final theme = AppTheme.of(context);
return MaterialApp(
theme: theme!.isDarkMode ? ThemeData.dark() : ThemeData.light(),
home: HomeScreen(),
);
},
),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = AppTheme.of(context);
return Scaffold(
appBar: AppBar(title: Text('InheritedWidget Theme')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Current Theme: ${theme!.isDarkMode ? "Dark" : "Light"}',
style: TextStyle(fontSize: 20),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => theme.toggleTheme(),
child: Text('Toggle Theme'),
),
],
),
),
);
}
}
Vantagens
- Embutido no Flutter — nenhuma dependência externa
- Rebuilds de widget eficientes
- Fundamento para entender outras soluções
- Controle direto sobre a lógica de propagação
Desvantagens
- Código de boilerplate verboso
- Requer wrapper StatefulWidget
- Fácil de cometer erros
- Não é amigável para iniciantes
Use-o quando:
- Aprendendo como o gerenciamento de estado do Flutter funciona internamente
- Construindo soluções personalizadas de gerenciamento de estado
- Você precisa de controle muito específico sobre a propagação de estado
3. Provider — A Solução Recomendada pelo Flutter
Quando o estado do seu app precisa ser compartilhado entre múltiplos widgets, o Provider vem ao resgate.
Provider baseia-se em Inversão de Controle — em vez de widgets possuírem o estado, um provedor o expõe para que outros o consumam. A equipe do Flutter recomenda oficialmente o Provider para aplicações de tamanho médio.
Configuração
Adicione isso ao seu pubspec.yaml
:
dependencies:
provider: ^6.0.5
Exemplo: Aplicativo de Contador com Provider
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: MyApp(),
),
);
}
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: CounterScreen());
}
}
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counter = context.watch<CounterModel>();
return Scaffold(
appBar: AppBar(title: Text('Provider Counter')),
body: Center(
child: Text('Count: ${counter.count}', style: TextStyle(fontSize: 24)),
),
floatingActionButton: FloatingActionButton(
onPressed: context.read<CounterModel>().increment,
child: Icon(Icons.add),
),
);
}
}
Vantagens
- Atualizações reativas e eficientes
- Separação clara entre UI e lógica
- Bem documentado e com suporte da comunidade
Desvantagens
- Slightly mais boilerplate do que
setState()
- Provedores aninhados podem se tornar complexos
Use-o quando:
- Você precisa de estado compartilhado entre múltiplos widgets
- Deseja um padrão reativo sem complexidade
- Seu app está crescendo além do tamanho de protótipo
4. Riverpod — Evolução Moderna do Provider
Riverpod é uma reescrita completa do Provider que remove sua dependência de BuildContext e adiciona segurança de compilação. Foi projetado para resolver as limitações do Provider, mantendo a mesma filosofia.
Configuração
Adicione isso ao seu pubspec.yaml
:
dependencies:
flutter_riverpod: ^2.4.0
Exemplo: Perfil de Usuário com Riverpod
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Modelo
class UserProfile {
final String name;
final int age;
UserProfile({required this.name, required this.age});
UserProfile copyWith({String? name, int? age}) {
return UserProfile(
name: name ?? this.name,
age: age ?? this.age,
);
}
}
// State Notifier
class ProfileNotifier extends StateNotifier<UserProfile> {
ProfileNotifier() : super(UserProfile(name: 'Guest', age: 0));
void updateName(String name) {
state = state.copyWith(name: name);
}
void updateAge(int age) {
state = state.copyWith(age: age);
}
}
// Provider
final profileProvider = StateNotifierProvider<ProfileNotifier, UserProfile>((ref) {
return ProfileNotifier();
});
// Provider computado
final greetingProvider = Provider<String>((ref) {
final profile = ref.watch(profileProvider);
return 'Hello, ${profile.name}! Você tem ${profile.age} anos.';
});
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: ProfileScreen());
}
}
class ProfileScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final profile = ref.watch(profileProvider);
final greeting = ref.watch(greetingProvider);
return Scaffold(
appBar: AppBar(title: Text('Riverpod Profile')),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(greeting, style: TextStyle(fontSize: 20)),
SizedBox(height: 30),
TextField(
decoration: InputDecoration(labelText: 'Nome'),
onChanged: (value) {
ref.read(profileProvider.notifier).updateName(value);
},
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Idade: ${profile.age}', style: TextStyle(fontSize: 18)),
SizedBox(width: 20),
ElevatedButton(
onPressed: () {
ref.read(profileProvider.notifier).updateAge(profile.age + 1);
},
child: Text('+'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
if (profile.age > 0) {
ref.read(profileProvider.notifier).updateAge(profile.age - 1);
}
},
child: Text('-'),
),
],
),
],
),
),
);
}
}
Vantagens
- Nenhuma dependência de BuildContext — acesse o estado em qualquer lugar
- Segurança de compilação — capture erros antes da execução
- Maior testabilidade
- Poderosa composição de estado
- Nenhuma vazamento de memória
Desvantagens
- API diferente das padrões tradicionais do Flutter
- Curva de aprendizado mais acentuada do que o Provider
- Comunidade menor (mas crescendo rapidamente)
Use-o quando:
- Iniciando um novo projeto Flutter
- Deseja segurança de tipo e garantias de compilação
- Dependências complexas e composições de estado
- Trabalhando em aplicações de tamanho médio a grande
5. BLoC (Business Logic Component) — Para Aplicações Empresariais
O BLoC pattern é uma arquitetura mais avançada que separa completamente a lógica de negócios da interface do usuário usando streams.
É ideal para aplicações de grande escala ou empresariais, onde transições de estado previsíveis e testáveis são essenciais.
Configuração
Adicione esta dependência ao seu projeto:
dependencies:
flutter_bloc: ^9.0.0
Exemplo: Aplicativo de Contador com BLoC
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// Eventos
abstract class CounterEvent {}
class Increment extends CounterEvent {}
// Bloc
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<Increment>((event, emit) => emit(state + 1));
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterBloc(),
child: MaterialApp(home: CounterScreen()),
);
}
}
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Bloc Counter')),
body: Center(
child: BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Text('Count: $count', style: TextStyle(fontSize: 24));
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<CounterBloc>().add(Increment()),
child: Icon(Icons.add),
),
);
}
}
Vantagens
- Escalável e mantível para aplicações complexas
- Separação clara das camadas
- Fácil de testar unidades
Desvantagens
- Mais boilerplate
- Curva de aprendizado mais acentuada
Use-o quando:
- Construindo projetos empresariais ou de longo prazo
- Precisa de lógica previsível e testável
- Múltiplos desenvolvedores trabalham em diferentes módulos da aplicação
6. GetX — A Solução Leve e Completa
GetX é uma solução de gerenciamento de estado minimalista, mas poderosa, que também inclui roteamento, injeção de dependência e utilitários. É conhecido por ter o menor boilerplate de todas as soluções.
Configuração
Adicione isso ao seu pubspec.yaml
:
dependencies:
get: ^4.6.5
Exemplo: Lista de Compras com GetX
import 'package:flutter/material.dart';
import 'package:get/get.dart';
// Controlador
class ShoppingController extends GetxController {
// Lista observável
var items = <String>[].obs;
// Contador observável
var totalItems = 0.obs;
void addItem(String item) {
if (item.isNotEmpty) {
items.add(item);
totalItems.value = items.length;
}
}
void removeItem(int index) {
items.removeAt(index);
totalItems.value = items.length;
}
void clearAll() {
items.clear();
totalItems.value = 0;
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'GetX Shopping List',
home: ShoppingScreen(),
);
}
}
class ShoppingScreen extends StatelessWidget {
// Inicializa o controlador
final ShoppingController controller = Get.put(ShoppingController());
final TextEditingController textController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GetX Shopping List'),
actions: [
Obx(() => Padding(
padding: EdgeInsets.all(16),
child: Center(
child: Text(
'Items: ${controller.totalItems}',
style: TextStyle(fontSize: 18),
),
),
)),
],
),
body: Column(
children: [
Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: TextField(
controller: textController,
decoration: InputDecoration(
hintText: 'Enter item name',
border: OutlineInputBorder(),
),
),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
controller.addItem(textController.text);
textController.clear();
},
child: Icon(Icons.add),
),
],
),
),
Expanded(
child: Obx(() {
if (controller.items.isEmpty) {
return Center(
child: Text('No items in your list'),
);
}
return ListView.builder(
itemCount: controller.items.length,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text(controller.items[index]),
trailing: IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () => controller.removeItem(index),
),
);
},
);
}),
),
if (controller.items.isNotEmpty)
Padding(
padding: EdgeInsets.all(16),
child: ElevatedButton(
onPressed: () {
Get.defaultDialog(
title: 'Clear List',
middleText: 'Are you sure you want to clear all items?',
textConfirm: 'Yes',
textCancel: 'No',
onConfirm: () {
controller.clearAll();
Get.back();
},
);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
minimumSize: Size(double.infinity, 50),
),
child: Text('Clear All'),
),
),
],
),
);
}
}
Vantagens
- Mínimo boilerplate — desenvolvimento mais rápido
- Solução completa (estado, roteamento, DI, snackbars, diálogos)
- Não precisa de BuildContext
- Muito leve e rápido
- Fácil de aprender
Desvantagens
- Menos “Flutter-like” comparado a Provider/BLoC
- Pode levar a acoplamento apertado se não for cuidadoso
- Ecossistema menor do que o Provider
- Alguns desenvolvedores acham que é “mágico demais”
Use-o quando:
- Prototipagem rápida ou desenvolvimento de MVP
- Aplicações pequenas a médias
- Deseja mínimo boilerplate
- Equipe prefere simplicidade sobre arquitetura rigorosa
Escolhendo a Solução de Gerenciamento de Estado Certa
Solução | Complexidade | Boilerplate | Curva de Aprendizado | Melhor Para |
---|---|---|---|---|
🧱 setState() |
Baixa | Mínima | Fácil | Estado local simples, protótipos |
🏗️ InheritedWidget |
Média | Alta | Média | Aprendendo internos do Flutter, soluções personalizadas |
🪄 Provider |
Baixa-Média | Baixa | Fácil | Maioria dos apps, estado compartilhado |
🔒 Riverpod |
Média | Baixa-Média | Média | Apps modernos, segurança de tipo |
📦 BLoC |
Alta | Alta | Alta | Apps empresariais, lógica de negócios complexa |
⚡ GetX |
Baixa | Mínima | Fácil | Desenvolvimento rápido, MVPs |
Guia Rápido para Decisão:
Complexidade do App | Abordagem Recomendada | Caso de Uso Exemplo |
---|---|---|
🪶 Simples | setState() ou GetX |
Alternar botões, animações, pequenos widgets |
⚖️ Média | Provider ou Riverpod |
Temas compartilhados, configurações do usuário, cache de dados |
🏗️ Complexa | BLoC ou Riverpod |
E-commerce, apps de chat, dashboards financeiros |
Pense Final
Não existe uma abordagem universal para o gerenciamento de estado no Flutter.
Aqui está uma abordagem prática:
- Comece simples com
setState()
para widgets e protótipos simples - Aprenda os fundamentos com InheritedWidget para entender como o Flutter funciona internamente
- Avance para Provider conforme seu app cresce e precisa de estado compartilhado
- Considere Riverpod para novos projetos onde segurança de tipo e padrões modernos importam
- Adote BLoC para aplicações empresariais com lógica de negócios complexa
- Experimente GetX quando a velocidade de desenvolvimento e o mínimo de boilerplate forem prioridades
Principais Lições:
✅ Não sobre-engenheire — use setState()
até que precise de mais
✅ Provider e Riverpod são excelentes escolhas para a maioria das aplicações
✅ BLoC brilha em equipes grandes e aplicações complexas
✅ GetX é ótimo para velocidade, mas fique atento ao acoplamento
✅ Entender InheritedWidget ajuda você a dominar qualquer solução
A chave é equilibrar simplicidade, escalabilidade e manutenibilidade — e escolher a ferramenta certa para suas necessidades específicas, expertise da equipe e requisitos do projeto.
Links para Bibliotecas de Gerenciamento de Estado do Flutter
- Documentação Oficial do Flutter sobre Gerenciamento de Estado
- Pacote Provider
- Documentação do Riverpod
- Biblioteca BLoC
- Documentação do GetX