Mixins e Classes de Base: Uma receita para o sucesso em Flutter

Mariana Castanheira
Flutter Portugal
Published in
6 min readOct 14, 2019

--

Foto de Matt Briney em Unplash

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 mixinss.

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, mixins 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 codeque possa ser usada noutro Person, mas não seja utilizada por todos os Persons? mixins resolvem esse problema.

Primeiro, precisamos de criar uma classe mixine expor os novos métodos que queremos usar.

Com a palavra-chave withpodemos 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 Animalque tem uma child Squirrel, também podemos ter um Squirrelque consegue code()! Para prevenir esta situação, podemos “bloquear” o uso do mixinpara 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 Personpara 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 thinkin. Mike:

Ao adquirir os conceitos de ambos base classes e mixins, 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 mixins.

Em ambos os casos, temos um título de ecrã definido; vamos criar uma base classque tem um método para fornecer o nome do nosso ecrã, chamada BasePage. Também vamos aplicar os mixins 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 BasePagee uma BaseState<BasePage>que estendem SatefulWidgete 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 mixintem 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 mixincriado 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 novo GlobalKey<ScaffoldState>
  • Criar um novo mixinque nos deixa exibir mensagens de erro enviadas pelo blocna página, usando um Snackbar

No nosso BaseBloc, estamos apenas a expôr um Sinke um Stream , de forma a transmitir mensagens de erro.

Como nós não queremos outras interações com o bloc, o nosso HomeBlocvai só estender esta classe.

Prosseguimos, ao alterar o construtor da nossa BasePagepara 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 scaffoldKeypara 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 overrideos seus métodos. Isto é útil, já que no nosso StatefulWidgetpodemos ouvir ( listen) os streamsdo blocno método initState. Assim. Para exibir as mensagens de erro, podemos criar um mixinque faz override ao método initStatee fornece métodos para mostrar a mensagem de erro correta numa Snackbar.

Finalmente, podemos adicioná-lo à nossa classe HomePageao adicioná-lo depois do mixin BasicPage.

Conclusão

E lá vamos nós! ✌️ Agora podemos utilizar ambos, mixins e abstract classes, 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 mixins como ErrorHandlingMixinpara 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 mixins é 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 mixins e classes base, o compilador não sabe qual escolher. Pode ler mais sobre isto no artigo de Shubham Soni sobre mixins em Flutter.

Finalmente, e tal como sugerido por Remi Rousselet, devemos estar atentos a como o uso extensivo de mixins pode ser considerado anti-padrões, podendo-se ler mais neste artigo de Dan Abramov.

--

--

Mariana Castanheira
Flutter Portugal

Translator (English-Portuguese/Portuguese-English). Helping Flutter Portugal for the time being.