DEV Community

Cover image for Entendendo State Pattern - Flutter
Adryanne Kelly
Adryanne Kelly

Posted on

Entendendo State Pattern - Flutter

No universo Flutter, encontramos diversas formas de lidar com gerência de estado e de aplicar as melhores práticas da linguagem em determinados contextos, nesse artigo vou lhe apresentar mais uma forma para essa coleção! Vamos conhecer um pouco sobre o State Pattern, que promete obedecer os princípios de responsabilidade única e composição de uma forma mais organizada, limpa e de fácil manutenção.

Tópicos

O que é o State Pattern?

No site de design patterns in Dart, podemos encontrar a seguinte definição, que é uma maneira simples e direta de dizer do que se trata o nosso State Pattern:

O State pattern é utilizado na programação para encapsular comportamentos diferentes para o mesmo objeto, com base no seu estado interno. Esta pode ser uma forma mais limpa de um objeto alterar o seu comportamento em tempo de execução sem recorrer a declarações condicionais, melhorando assim a manutenção.

Isso quer dizer que cada estado do nosso objeto estará separado por classes, que serão extensões/variações de uma classe de estado principal de um determinado objeto.

Mas como assim?

representação estados da água

Imagine que você tem um objeto Agua, para representar o estado desta Agua você cria um EstadoDaAgua, logo as variações de estado de Agua poderiam ser Solido, Liquido e Gasoso, ou seja:

abstract class EstadoDaAgua { // } class Solido extends EstadoDaAgua { // } class Liquido extends EstadoDaAgua { // } class Gasoso extends EstadoDaAgua { // } 
Enter fullscreen mode Exit fullscreen mode

O State Pattern é usado inclusive no padrão BLoC. Se você usa padrão BLoC, obrigatoriamente já estará usando State Pattern.

Quando devo usar State Pattern?

  • Quando seu objeto se comporta diferente dependendo do seu estado.
  • Quando o número de estados é grande e o código do estado muda frequentemente.
  • Quando sua classe tiver uma quantidade massiva de condicionais que alteram a forma como a classe se comporta de acordo com os valores dos campos que ela contém.
  • Quando tiver muito código duplicado de estados e transições semelhantes.

State Pattern na prática

Agora, vamos ver um exemplo de uso de State Pattern na prática. Observando a classe abaixo, percebemos que o estado está sendo definido a partir de variáveis usando ChangeNotifier:

class CategoryStore extends ChangeNotifier { List<String> categories = []; bool isLoading = false; String error = ''; IApiDatasource apiDatasource = ApiDatasource(); void getCategories() async { isLoading = true; await apiDatasource.getCategories().then((response) { response.fold( (left) => error = left.message; (right) => categories = right; ) }); isLoading = false; notifyListeners(); } } 
Enter fullscreen mode Exit fullscreen mode

O método fold() utilizado no exemplo pertence ao Either Type, um elemento da programação funcional utilizado para representar um valor que tem qualquer um dos dois tipos especificados. O Either é comumente usado para representar um valor de sucesso ou um valor de falha, assim como exemplificado acima onde left representa o valor de erro e o right o valor de sucesso.

A classe acima contém uma função que inicia com o carregamento (isLoading) e, durante esse processo, ela aguarda que os dados provenientes de uma API sejam armazenados na variável categories. Quando o carregamento é concluído, a função recebe o valor falso.

Poderíamos organizar esse código usando State Pattern da seguinte forma:

  • Classe de Estados

Declaramos os estados com suas respectivas classes.

abstract class CategoryState {} class CategoryInitial extends CategoryState {} class CategoryLoading extends CategoryState {} class CategoryLoaded extends CategoryState { final List<String> categories; CategoryLoaded(this.categories); } class CategoryError extends CategoryState { final String message; CategoryError(this.message); } 
Enter fullscreen mode Exit fullscreen mode
  • Classe de Store

Na função getCategories() descartamos a variável isLoading e utilizaremos a variável value (variável que representa o estado no ValueNotifier, que por sua vez é do tipo CategoryState) setando a classe CategoryLoading para receber nosso estado.

Depois disso, no método fold() se minha requisição deu erro ,value receberá o estado de Error com a mensagem (left) e se estiver dado tudo certo receberá o Loaded com seus dados carregados na variável right e assim, acabamos por descartar as variáveis categories e èrror

class CategoryStore extends ValueNotifier<CategoryState> { CategoryStore() : super(CategoryInitial()); IApiDatasource apiDatasource = ApiDatasource(); void getCategories() async { value = CategoryLoading(); await apiDatasource.getCategories().then((response) { response.fold( (left) => value = CategoryError(left.message); (right) => value = CategoryLoaded(right); ) }); } } 
Enter fullscreen mode Exit fullscreen mode

value é um get de Value Notifier, onde contém o estado atual da nossa classe Store

  • Exemplo em página
 class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { //Instancia de CategoryStore criada  CategoryStore store = CategoryStore(); // iniciando função ao carregar a página @override void initState() { store.getCategories(); super.initState(); } @override void dispose() { // TODO: implement dispose super.dispose(); store.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Expanded( // "Escutando a store" child: ValueListenableBuilder( valueListenable: store, builder: (context, value, child) { //se o value receber erro retorna a mensagem if (value is CharacterError) { return Center( child: Text( 'Erro ao carregar categorias: ${value.message}', ), ); } // se o value receber sucesso/loaded retorna a lista // de categorias if (value is CharacterLoaded) { return ListView.builder( controller: store.scroll, itemCount: value.categories.length, itemBuilder: (context, index) { final category = value.categories[index]; return Text(category); } ); } // em estado de loading ou inicial ficará carregando return const Center(child: CircularProgressIndicator()); } ), ); } } 
Enter fullscreen mode Exit fullscreen mode

E pronto, já implementamos nosso State Pattern! E como vantagem: seguimos o Princípio de Responsabilidade Única e Open/Closed e simplificamos o código eliminando condicionais que poderiam deixar o nosso código poluído.

Conclusão

Muito obrigada por ter lido até aqui, como este é um artigo sobre o State Pattern, acabei por não falar muito sobre o ValueNotifier e outras coisas que escolhi utilizar no exemplo, porém vou estar deixando abaixo alguns links que podem ajudar a entender, além de também um vídeo do meu sensei @redrodrigoc explicando direitinho como o State Pattern funciona, vale a pena dar uma olhada. Espero que tenham gostado, até a próxima! 💙

Referências

Meus agradecimentos ao @redrodrigoc e a @cherryramatis por todo apoio e ajuda com este artigo 💙

Top comments (5)

Collapse
 
redrodrigoc profile image
Rodrigo Castro

Ótimo conteúdo, parabéns.

Collapse
 
adryannekelly profile image
Adryanne Kelly • Edited

Muito obrigada, tive um bom professor kk 💙

Collapse
 
williamcawi profile image
williamcawi

Muito bom, gostei desse padrão!

Collapse
 
rafael_farias_ff6339cd10b profile image
Rafael Farias

Muito bom conteúdo, parabéns!!

Collapse
 
adryannekelly profile image
Adryanne Kelly

Muito obrigada ❤️