6 Maneiras de Gerenciar Estado em Aplicativos Flutter (Com Exemplos de Código)

Como gerenciar o estado no Flutter

Conteúdo da página

Neste artigo, exploraremos seis maneiras populares de gerenciar o estado no Flutter em aplicativos, 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 aplicativo lida com mudanças de dados e atualiza a UI de forma eficiente. O Flutter oferece várias estratégias para gerenciar estado — desde simples até altamente escaláveis.

As estratégias de gerenciamento de estado no Flutter são:

  1. 🧱 setState() — A abordagem nativa e mais simples
  2. 🏗️ InheritedWidget — A base do Flutter para propagação de estado
  3. 🪄 Provider — A solução recomendada para a maioria dos aplicativos
  4. 🔒 Riverpod — Evolução moderna e segura em tempo de compilação do Provider
  5. 📦 Bloc — Para aplicativos escaláveis de nível empresarial
  6. GetX — Solução leve e completa (all-in-one)

flutter troubles mega tracktor


1. Usando setState() — O Básico

A maneira mais simples de gerenciar estado no Flutter é usando o método nativo setState() em um StatefulWidget.

Esta abordagem é ideal para estado de UI local — onde o estado pertence a um único widget e não precisa ser compartilhado em todo o aplicativo.

Exemplo: App 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),
      ),
    );
  }
}

Prós

  • Muito simples de implementar
  • Excelente para estado de UI local ou temporário
  • Sem dependências externas

Contras

  • Não escala para aplicativos grandes
  • Difícil de compartilhar estado entre widgets
  • Lógica misturada com UI

Use quando:

  • Fazendo protótipos ou construindo widgets pequenos
  • Lidando com estado de UI isolado (ex: alternar um botão, mostrar um modal)

2. InheritedWidget — A Base do Flutter

InheritedWidget é o mecanismo de baixo nível que o Flutter usa para propagar dados para baixo na árvore de widgets. A maioria das soluções de gerenciamento de estado (incluindo o Provider) é construída sobre ele.

Compreender o InheritedWidget ajuda você a entender como o gerenciamento de estado do Flutter funciona internamente.

Exemplo: Gerenciador de Tema com InheritedWidget

import 'package:flutter/material.dart';

// O InheritedWidget que mantém 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'),
            ),
          ],
        ),
      ),
    );
  }
}

Prós

  • Incorporado ao Flutter — sem dependências externas
  • Recriação de widgets eficiente
  • Base para entender outras soluções
  • Controle direto sobre a lógica de propagação

Contras

  • Código boilerplate verboso
  • Requer um wrapper StatefulWidget
  • Fácil de cometer erros
  • Não amigável para iniciantes

Use quando:

  • Aprendendo como o gerenciamento de estado do Flutter funciona internamente
  • Construindo soluções de gerenciamento de estado personalizadas
  • 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 aplicativo precisa ser compartilhado entre múltiplos widgets, o Provider vem para salvar.

Provider é baseado em Inversão de Controle — em vez de widgets possuírem o estado, um provedor o expõe para que outros consumam. A equipe do Flutter recomenda oficialmente isso para aplicativos de tamanho médio.

Configuração

Adicione isso ao seu pubspec.yaml:

dependencies:
  provider: ^6.0.5

Exemplo: App 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),
      ),
    );
  }
}

Prós

  • Atualizações reativas e eficientes
  • Separação limpa entre UI e lógica
  • Bem documentado e suportado pela comunidade

Contras

  • Um pouco mais de boilerplate que setState()
  • Provedores aninhados podem ficar complexos

Use quando:

  • Você precisa de estado compartilhado entre múltiplos widgets
  • Você quer um padrão reativo sem complexidade
  • Seu aplicativo está crescendo além do tamanho de um protótipo

4. Riverpod — A Evolução Moderna do Provider

Riverpod é uma reescrita completa do Provider que remove a dependência do BuildContext e adiciona segurança em tempo de compilação. Ele 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';

// Model
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();
});

// Computed provider
final greetingProvider = Provider<String>((ref) {
  final profile = ref.watch(profileProvider);
  return 'Hello, ${profile.name}! You are ${profile.age} years old.';
});

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: 'Name'),
              onChanged: (value) {
                ref.read(profileProvider.notifier).updateName(value);
              },
            ),
            SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('Age: ${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('-'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

Prós

  • Sem dependência de BuildContext — acesse o estado em qualquer lugar
  • Segurança em tempo de compilação — capture erros antes da execução
  • Melhor testabilidade
  • Composição de estado poderosa
  • Sem vazamentos de memória

Contras

  • API diferente dos padrões tradicionais do Flutter
  • Curva de aprendizado mais íngreme que o Provider
  • Comunidade menor (mas crescendo rapidamente)

Use quando:

  • Iniciando um novo projeto Flutter
  • Você quer segurança de tipo e garantias em tempo de compilação
  • Dependências e composições de estado complexas
  • Trabalhando em aplicativos de médio a grande porte

5. BLoC (Componente de Lógica de Negócio) — Para Aplicativos Empresariais

O padrão BLoC é uma arquitetura mais avançada que separa completamente a lógica de negócios da UI usando streams (fluxos).

É ideal para aplicativos 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: App de Contador com BLoC

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

// Events
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),
      ),
    );
  }
}

Prós

  • Escalável e mantível para aplicativos complexos
  • Separação clara de camadas
  • Fácil de testar com testes unitários

Contras

  • Mais boilerplate
  • Curva de aprendizado mais íngreme

Use quando:

  • Construindo projetos empresariais ou de longo prazo
  • Você precisa de lógica previsível e testável
  • Vários desenvolvedores trabalham em diferentes módulos do aplicativo

6. GetX — A Solução Leve e Completa (All-in-One)

GetX é uma solução de gerenciamento de estado minimalista e poderosa que também inclui roteamento, injeção de dependência e utilitários. É conhecida 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';

// Controller
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 {
  // Inicializar controller
  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'),
              ),
            ),
        ],
      ),
    );
  }
}

Prós

  • Boilerplate mínimo — desenvolvimento mais rápido
  • Solução completa (estado, roteamento, DI, snackbars, dialogs)
  • Sem necessidade de BuildContext
  • Muito leve e rápido
  • Fácil de aprender

Contras

  • Menos “estilo Flutter” comparado ao Provider/BLoC
  • Pode levar a acoplamento forte se não tiver cuidado
  • Ecossistema menor que o Provider
  • Alguns desenvolvedores acham “mágico demais”

Use quando:

  • Prototipação rápida ou desenvolvimento de MVP
  • Aplicativos pequenos a médios
  • Você quer boilerplate mínimo
  • A equipe prefere simplicidade em vez de arquitetura estrita

Escolhendo a Solução de Gerenciamento de Estado Certa

Solução Complexidade Boilerplate Curva de Aprendizado Melhor Para
🧱 setState() Baixa Mínimo Fácil Estado local simples, protótipos
🏗️ InheritedWidget Média Alta Média Aprender a fundo do Flutter, soluções personalizadas
🪄 Provider Baixa-Média Baixo Fácil A maioria dos apps, estado compartilhado
🔒 Riverpod Média Baixo-Médio Médio Apps modernos, segurança de tipos
📦 BLoC Alta Alta Íngreme Apps empresariais, lógica de negócios complexa
GetX Baixa Mínimo Fácil Desenvolvimento rápido, MVPs

Guia Rápido de Decisão:

Complexidade do App Abordagem Recomendada Caso de Uso Exemplo
🪶 Simples setState() ou GetX Botões de alternância, animações, widgets pequenos
⚖️ Médio Provider ou Riverpod Temas compartilhados, configurações de usuário, cache de dados
🏗️ Complexo BLoC ou Riverpod E-commerce, apps de chat, painéis financeiros

Pensamentos Finais

Não existe uma solução única para o gerenciamento de estado no Flutter.

Aqui está uma abordagem prática:

  • Comece pequeno com setState() para widgets simples e protótipos
  • Aprenda os fundamentos com InheritedWidget para entender como o Flutter funciona internamente
  • Progrida para o Provider conforme seu aplicativo cresce e precisa de estado compartilhado
  • Considere o Riverpod para novos projetos onde segurança de tipos e padrões modernos importam
  • Adote o BLoC para grandes aplicativos empresariais com lógica de negócios complexa
  • Experimente o GetX quando o desenvolvimento rápido e boilerplate mínimo forem prioridades

Principais Pontos:

✅ Não superengenharia — use setState() até precisar de mais
✅ Provider e Riverpod são excelentes escolhas para a maioria das aplicações
✅ BLoC brilha em grandes equipes e aplicativos complexos
✅ GetX é ótimo para velocidade, mas tenha cuidado com o acoplamento
✅ Entender o 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, experiência da equipe e requisitos do projeto.