DEV Community

Lucien Risso Correia
Lucien Risso Correia

Posted on • Edited on

Gerência de Estado no Flutter

O ano era 2021 e a comunidade Flutter estava agitada demais com algumas tretas envolvendo o time do Flutter e desenvolvedores da comunidade, nessa mesma época o tal state management estava no hype de assuntos da comunidade. Fiz um texto originalmente no Medium, que também está publicado aqui, tirando sarro dessas polêmicas e satirizando os principais packages de gerenciamento de estado do Flutter.

Em projetos futuros a esse texto ainda continuou a discussão sobre qual package utilizar para isso, e a treta sempre foi grande. Chegamos em 2024 e sinto que isso deixou de ser um richa e finalmente os devs Flutter entenderam que no final tudo é Streams ou Observers, ou talvez não...


Observer

O Observer Pattern, também conhecido como Publish/Subscribe, é um padrão de design que define um mecanismo de comunicação entre objetos. Nesse padrão, um objeto, chamado Subject, mantém uma lista de objetos Observers que dependem dele. Quando o estado do Subject muda, ele notifica todos os Observers, que podem então atualizar suas próprias interfaces.

No Flutter esse designer é implementado no ChangeNotifier, onde se pode criar uma classe com as propriedades desejadas pro estado que quer gerenciar e métodos que manipulam e notificam a mudança de estado pros Observers. Veja um exemplo de uso básico de gerenciamento de estado com Observer em Flutter:

class MyState extends ChangeNotifier {
  int count = 0;

  void incrementCount() {
    count++;
    notifyListeners();
  }
}

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  MyState state = MyState();

  @override
  void initState() {
    super.initState();
    state.addListener(() {
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return Text('Count: ${state.count}');
  }
}
Enter fullscreen mode Exit fullscreen mode

Nesse exemplo, a classe MyState é um Subject. Ela define uma interface para registrar e remover Observers (o método addListener()) e notifica os Observers quando seu estado muda (o método notifyListeners()).

A classe MyWidget é um Observer. Ela se registra como um Observer da classe MyState no método initState(). Quando o estado da classe MyState muda, o método update() da classe MyWidget é chamado, o que faz com que o widget seja reconstruído.

Com o Provider podemos simplificar a parte de widget e retirar a parte de implementação do trecho do initState, já que o próprio widget dele já faz essa reconstrução quando recebe notificação que o estado mudou. Com isso temos pequenas mudanças no código de widget e ele ficaria assim:

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => MyState(),
      child: Consumer<MyState>(
        builder: (context, state, child) {
          return Column(
            children: [
              Text('Count: ${state.count}'),
              ElevatedButton(
                onPressed: state.incrementCount,
                child: Text('Incrementar'),
              ),
            ],
          );
        },
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Lembre-se: o Provider tem como foco ser uma biblioteca para injeção de dependência utilizando a arvore de widgets, a classe Consumer auxilia no uso das dependências injetadas no contexto de Widget, o foco do Provider não é gerenciar estado.


Stream

Stream é uma sequência assíncrona de eventos. Ela pode ser usada para representar dados que mudam ao longo do tempo, como a localização do usuário, a entrada do teclado ou a saída de um sensor.

Um exemplo de uso se Stream para gerenciar estado no Flutter com um contador progressivo que incrementa o valor a cada segundo:

class MyState {
  int count = 0;

  Stream<int> get countStream =>
      Stream.periodic(const Duration(seconds: 1), (_) => count++);
}

class MyWidget extends StatelessWidget {
  final MyState state = MyState();

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<int>(
      stream: state.countStream,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Text('Count: ${snapshot.data}');
        } else {
          return Text('Aguardando...');
        }
      },
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Nesse exemplo usamos o StremBuilder para poder atualizar o widget a cada troca de valor, mas também podemos fazer sem ele com o famoso setState escutando os eventos da Stream com o listen:

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  final MyState state = MyState();

  @override
  void initState() {
    super.initState();
    state.countStream.listen((count) {
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return Text('Count: ${state.count}');
  }
}
Enter fullscreen mode Exit fullscreen mode

E é sobre isso que venho lembrar e ajudar a fixar na cabecinha dos devs iniciantes em Flutter: quem gerencia o estado é você e não o package. O que quero lembrar é que independente de está usando BLoC ou MobX, Riverpod ou Solidart, GetX ou setState, o resultado final dependerá de como você escreveu a solução. E se não gostou de nenhuma implementação de Observer dos packages, crie sua própria, no final ou é feito em cima de Stream ou em cima de Observer.

Vou listar qual usa o quê pra exemplificar:

Packages que utilizam Stream:

  • BLoC
  • RxDart
  • GetX

Packages que utilizam Observer:

  • MobX
  • ChangeNotifier
  • Flutter Hooks

Por trás dos panos eles utilizam basicamente a mesma lógica, porém com implementações diferentes que mudam como será escrito o código. Mas igualmente, quem faz o gerenciamento é quem escreve a aplicação em si. Além disso o que ocorre muito é confundirem state managment com dependencie injection, no caso de GetX ou Provider por exemplo, que possuem essa implementação a mais para compor o package e tornar mais completo. São coisas que costumam serem necessitas juntamente porém já não são mais em si state managment.


Bom códigos e best reguards!

Sentry mobile image

App store rankings love fast apps - mobile vitals can help you get there

Slow startup times, UI hangs, and frozen frames frustrate users—but they’re also fixable. Mobile Vitals help you measure and understand these performance issues so you can optimize your app’s speed and responsiveness. Learn how to use them to reduce friction and improve user experience.

Read full post →

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay