DEV Community

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

Posted on

16 8 8 8 9

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!

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up