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 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:

  1. 🧱 setState() — A abordagem mais simples embutida
  2. 🏗️ InheritedWidget — Fundamento do Flutter para propagação de estado
  3. 🪄 Provider — A solução recomendada para a maioria dos apps
  4. 🔒 Riverpod — Evolução moderna e segura de compilação do Provider
  5. 📦 Bloc — Para aplicações escaláveis e empresariais
  6. GetX — Solução leve e completa

flutter troubles mega tracktor


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.