Mixins e Classes de Base: Uma receita para o sucesso em Flutter
Na Flutter Portugal acreditamos que o conhecimento tem de ser acessível a todos, independentemente do grau de conhecimento de línguas estrangeiras. Assim, vamos lançar uma pequena série de artigos que vão abrangir vários tópicos: Dados persistidos, State Management, Navegação, etc…
Este artigo foi originalmente escrito por Gonçalo Palma com o título “Mixins and Base Classes: A recipe for success in Flutter” e traduzido com autorização do autor.
Quando se desenvolve uma aplicação com múltiplos ecrãs, tendemos a reutilizar o mesmo pedaço de código em muitas classes: mostrar mensagens de erro, utilizar a mesma disposição de página e inicializar algumas dependências como, por exemplo, um Bloc. Tudo isto pode ser resolvido se estivermos a usar uma classe de base (base class) abstract
, porém, e se tivermos um conjunto de características/classes que nós queremos utilizar num ecrã particular, mas não noutros? Tendo em conta que uma classe não pode ser uma filha (child) de mais do que uma classe, deveremos nós criar diferentes classes de base, tantas quanto o número de combinações que temos? É por isso que temos mixins
s.
Mixins e Classes de Base: Uma introdução
Mixins deixam-nos adicionar um conjunto de “características” a uma classe, sem usar a hierarquia parent-child, permitindo-nos ter, na mesma classe, um pai (parent) e múltiplos componentes mixin
. Assim, já que não é um pai da nossa classe, mixin
s não permitem nenhuma declaração de construtores. Podem ler mais sobre isto neste artigo de Romain Rastel, com a advertência de que o Dart 2 tem agora a palavra-chave mixin
, como visto na documentação.
Mas como é que os mixins
funcionam? Vamos tomar como exemplo uma absrract class
Person
.
Podemos utilizar esta classe como um pai, usando a palavra-chave extend
, assim:
Com isto, podemos incicializar a classe e chamar o método do pai think()
.
Mas, e se quisermos adicionar novas funcionalidades ao Mike
? E se o Mike for um programador e precisa de uma função code
que possa ser usada noutro Person
, mas não seja utilizada por todos os Person
s? mixin
s resolvem esse problema.
Primeiro, precisamos de criar uma classe mixin
e expor os novos métodos que queremos usar.
Com a palavra-chave with
podemos adicionar este “elemento” à nossa classe Mike
:
E, como com o pai, podemos chamar todas as funções que criámos no Coder
.
Agora, todas as classes que usem o mixin
Coder
podem efetivamente programar. No entanto, surge um problema: isto significa que, se tivermos uma classe parent Animal
que tem uma child Squirrel
, também podemos ter um Squirrel
que consegue code()
! Para prevenir esta situação, podemos “bloquear” o uso do mixin
para uma classe e todas as classes que dele herdaram a palavra-chave on
:
Isto também nos dá uma ferramenta poderosa: agora podemos fazer override a métodos que estavam definidos na classe Person
para adicionar ou alterar a sua funcionalidade.
Chamar o super.think()
garante que ainda estamos a chamar o código que foi definido em Person
. O código acima dá-nos o seguinte output para o método think
in. Mike
:
Ao adquirir os conceitos de ambos base class
es e mixin
s, agora podemos aplicá-los às nossas aplicações Flutter.
Mixins e Classes Base: Um exemplo Flutter prático
Como é que podemos aplicar isto nas nossas aplicações Flutter?
Tomemos como exemplo os dois ecrãs seguintes:
A nossa aplicação tem vários ecrãs com a disposição mostrada acima. Em vez de copiar e colar a appbar (barra da aplicação) e o fundo para cada ecrã, podemos resolver o nosso problema ao utilizar mixin
s.
Em ambos os casos, temos um título de ecrã definido; vamos criar uma base class
que tem um método para fornecer o nome do nosso ecrã, chamada BasePage
. Também vamos aplicar os mixin
s só no StatefulWidgets
, já que as nossas classes vão manter e mudar o seu estado. Com isto, nós criamos duas classes para serem usadas nas nossas páginas: uma BasePage
e uma BaseState<BasePage>
que estendem SatefulWidget
e State<StatefulWidget>
, respetivamente.
Focando agora no segundo ecrã, já podemos criar o seu mixin
BasicPageMixin
personalizado, onde definimos o fundo e a appbar da nossa página.
Tendo em conta que o método body()
não tem um corpo (body), cada classe que use este mixin
tem de o implementar, assegurando que nós não nos esqueçamos de adicionar um corpo à nossa página.
Na captura de ecrã acima, vemos um FloatingActionButton
, mas poderemos não precisar dele para todos os ecrãs, então como é que o podemos definir? Ao declarar um novo método, fab()
, que por predefinição devolve um Container
. Se uma classe necessita de adicionar um FloatingActionButton
, eles podem substituir este método.
Com o nosso mixin
criado podemos agora aplicá-lo a uma nova página.
E, assim, só nos resta declarar um body()
e um possível widget fab()
para ser usado em cada ecrã, poupando-nos umas dúzias de linhas de código.
Combinar mixins
Sendo uma nova funcionalidade, alguns dos nossos ecrãs vão fazer chamadas a uma API (Interface de Programação de Aplicações) e, se ocorrer algum erro, precisaremos de exibir uma mensagem de erro na forma de um Snackbar. Além disso, decidimos utilizar a arquitectura BLoC, na qual precisamos de injetar um novo bloc de cada vez que uma página é criada. Estes dois problemas vão necessitar dos seguintes passos:
- Modificar a nossa
BasePage
, através da alteração do seu construtor com o novo parâmetro bloc. - Modificar
BaseState
, ao adicionar um novoGlobalKey<ScaffoldState>
- Criar um novo
mixin
que nos deixa exibir mensagens de erro enviadas pelobloc
na página, usando um Snackbar
No nosso BaseBloc
, estamos apenas a expôr um Sink
e um Stream
, de forma a transmitir mensagens de erro.
Como nós não queremos outras interações com o bloc
, o nosso HomeBloc
vai só estender esta classe.
Prosseguimos, ao alterar o construtor da nossa BasePage
para incluir o objeto do bloc
. Isto vai forçar-nos a mudar também todas as classes que o estendem para adicionar um bloc
igualmente aos seus construtores. O parâmetro bloc
é usado como um tipo Genérico de modo a que cada classe que o estende possa declarar o tipo correto de bloc que está a utilizar. Isto assegura que, quando o estamos a chamar na BaseState
, vamos obter o tipo correto de bloc
, permitindo-nos aceder aos seus métodos.
Quanto ao BaseState
, vamos declarar um scaffoldKey
para ser usado com o ScaffoldWidget
para que possamos exibir a Snackbar.
Tal como visto anteriormente, uma das curiosas propriedades dos mixin é que, se estiverem ligados a uma classe, eles podem override
os seus métodos. Isto é útil, já que no nosso StatefulWidget
podemos ouvir ( listen
) os streams
do bloc
no método initState
. Assim. Para exibir as mensagens de erro, podemos criar um mixin
que faz override ao método initState
e fornece métodos para mostrar a mensagem de erro correta numa Snackbar.
Finalmente, podemos adicioná-lo à nossa classe HomePage
ao adicioná-lo depois do mixin BasicPage
.
Conclusão
E lá vamos nós! ✌️ Agora podemos utilizar ambos, mixin
s e abstract class
es, para reutilizar o código nas nossas aplicação.
Talvez não precisemos de fazer uma base UI (Interface de Utilizador) para a nossa aplicação, mas podemos usar mixin
s como ErrorHandlingMixin
para mostrar uma mensagem de erro ao utilizador, um feedback quando estivermos a carregar uma página ou até mostrar um ecrã com “A Aplicação está Offline”.
No entanto, criar ambas as classes base e os mixin
s é um processo que precisa de alguma deliberação, caso contrário poderemos estar perante um “Deadly Diamond of Death”, em que quando a chamar um método que é declarado em ambos os mixin
s e classes base, o compilador não sabe qual escolher. Pode ler mais sobre isto no artigo de Shubham Soni sobre mixin
s em Flutter.
Finalmente, e tal como sugerido por Remi Rousselet, devemos estar atentos a como o uso extensivo de mixin
s pode ser considerado anti-padrões, podendo-se ler mais neste artigo de Dan Abramov.