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?
- Quando devo usar StatePattern?
- State Pattern na prática
- Conclusão
- Referências
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?
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 { // }
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(); } }
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 ondeleft
representa o valor de erro e oright
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); }
- 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); ) }); } }
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()); } ), ); } }
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
- Vídeo REDRODRIGO - State Pattern
- State Pattern com ValueNotifier - bwolf
- Design patterns in Dart
- Refactoring Guru - Design patterns - State
- Either Type
- Better Error Handling with Either type in Dart
- Package para uso de Either
- Understanding Flutter ValueNotifier
Meus agradecimentos ao @redrodrigoc e a @cherryramatis por todo apoio e ajuda com este artigo 💙
Top comments (5)
Ótimo conteúdo, parabéns.
Muito obrigada, tive um bom professor kk 💙
Muito bom, gostei desse padrão!
Muito bom conteúdo, parabéns!!
Muito obrigada ❤️