Na transição para uma nova tecnologia, perdemos as ferramentas usuais para o desenvolvimento. Em alguns casos, somos forçados a aturar sua ausência devido a algumas limitações técnicas, mas, se possível, carregamos as ferramentas conosco. Ao desenvolver aplicativos para Android, tomei o exemplo de uma arquitetura limpa proposta por Fernando Cejas como base. Compreendendo os padrões de design usados no Flutter, decidi abandonar essa arquitetura em favor do BLoC. Eu me acostumei rapidamente a esse modelo, ele é muito semelhante ao MVVM com o qual trabalhei anteriormente, mas não queria aturar um detalhe. Ao chamar métodos de repositório, tive que capturar exceções, convertê-las em algum tipo e, de acordo com o tipo, criar o estado necessário. Na minha opinião, isso atrapalha bastante o bloco e eu portado o tipo Either usado anteriormente em projetos Android baseados em Fernando.
Ou vieram de linguagens de programação funcionais. Ele fornece o valor de um dos tipos possíveis:
- Esquerda (em caso de falha);
- Certo (se for bem-sucedido).
Implementação básica do Either A implementação é muito básica, inferior às soluções em outros idiomas, mas lida com sua tarefa. Eu uso esse tipo como resultado de todos os métodos do repositório e o tratamento de exceções é transferido para a camada de dados. Isso elimina a necessidade de construções try / catch, o que torna o código mais legível.
Exemplo de tentativa / captura class ContactBloc { final ContactRepository contactRepository; ContactBloc(this.contactRepository); @override Stream<ContactState> mapEventToState(ContactEvent event) async* { if (event is GetContactEvent) { yield LoadContactState(); try { var contact = contactRepository.getById(event.id); yield ContactIsShowingState(contact); } on NetworkConnectionException catch (e) { yield NetworkExceptionState(e); } catch (e) { yield UnknownExceptionState(e); } } } } abstract class ContactRepository { Future<Contact>getById(int id); }
Exemplo com qualquer class ContactBloc { final ContactRepository contactRepository; ContactBloc(this.contactRepository); @override Stream<ContactState> mapEventToState(ContactEvent event) async* { if (event is GetContactEvent) { yield LoadContactState(); final either = contactRepository.getById(event.id); if (either.isRight) { final contact = either.right; yield ContactIsShowingState(contact); } else { final failure = either.left; if (failure is NetworkFailure) yield NetworkFailureState(failure); if (failure is UnknownFailure) yield UnknownFailureState(failure); } } } } abstract class ContactRepository { Future<Either<Failure, Contact>>getById(int id); }
Quanto à legibilidade, alguém pode argumentar. Talvez alguém esteja acostumado a tentar / pegar e tenha razão à sua maneira, na maioria das vezes isso é saboroso. Uma vantagem adicional é que nós mesmos podemos definir a hierarquia de falhas e retornar no lado esquerdo. Vamos fazer uma falha abstrata, tornar comum todos os recursos ServerFailure, NetworkFailure e alguns recursos específicos do ContactFailure para o recurso atual, com herdeiros. No bloco, saberemos exatamente qual das falhas esperar.
A desvantagem na implementação de Failure on Dart é a falta de classes seladas como no kotlin; caso contrário, não haveria ifs com o casting. A linguagem é jovem, se desenvolve ativamente e espero que chegue a hora e teremos ferramentas para escrever manipuladores de forma mais sucinta.
Alguém pode não gostar desta implementação, pode achar inútil, mas eu só queria apresentar a possibilidade de uma abordagem funcional para o tratamento de erros no Dart, embora o uso não tenha sido tão elegante quanto em outros idiomas.
Recursos:
Código fonte