Entendendo State Pattern - Flutter
Tags: flutter, designpatterns, dart, braziliandevs
Published: 2024-05-02 12:09:50

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?](#o-que-é-o-state-pattern)
- [Quando devo usar StatePattern?](#quando-devo-usar-state-pattern)
- [State Pattern na prática](#state-pattern-na-prática)
- [Conclusão](#conclusão)
- [Referências](#referências)

## O que é o State Pattern?

No site de [design patterns in Dart](https://scottt2.github.io/design-patterns-in-dart/state/), 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/miz1pxdip7jmcpg16odl.jpg)

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:

```dart
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](https://docs.flutter.dev/data-and-backend/state-mgmt/simple#changenotifier):

```dart
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](https://www.sandromaglione.com/articles/either-error-handling-functional-programming#either---functional-programming-error-handling), 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.
	
```dart
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](https://api.flutter.dev/flutter/foundation/ValueNotifier-class.html), 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`
	
```dart
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**
```dart

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](https://www.youtube.com/watch?v=kAk8P2LdgZ0)
- [State Pattern com ValueNotifier - bwolf](https://www.youtube.com/watch?v=Pp6C3TNVsl8&t=6s)
- [Design patterns in Dart](https://scottt2.github.io/design-patterns-in-dart/state/)
- [Refactoring Guru - Design patterns - State](https://refactoring.guru/design-patterns/state)
- [Either Type](https://www.sandromaglione.com/articles/either-error-handling-functional-programming#either---functional-programming-error-handling)
- [Better Error Handling with Either type in Dart](https://medium.com/nerd-for-tech/better-error-handling-with-either-type-in-dart-b91bef20d716)
- [Package para uso de Either](https://pub.dev/packages?q=fpdart)
- [Understanding Flutter ValueNotifier](https://www.dhiwise.com/post/flutter-valuenotifier-an-essential-tool-for-state-management)

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