Entendendo State Pattern – Flutter

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?
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 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);
}

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 💙

Leave a Reply

Your email address will not be published. Required fields are marked *