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
 
rafael_farias_ff6339cd10b profile image
Rafael Farias

Muito bom conteúdo, parabéns!!

Collapse
 
adryannekelly profile image
Adryanne Kelly

Muito obrigada ❤️

Collapse
 
williamcawi profile image
williamcawi

Muito bom, gostei desse padrão!