Um padrão de design para Flutter

Tempo de leitura: 20 minutes

Agora, neste artigo, vou mergulhar mais fundo na abordagem MVC. Isso será como ‘olhar sob o capô’ do BloC e do Redux. Neste caso, vamos entrar nas ervas daninhas do framework MVC conforme eu o implementei. Como seria, vai ficar um pouco complicado. Para alguns, seus olhos podem ficar vidrados, mas espera-se que usar o mesmo aplicativo de amostra nos ajude a seguir em frente. Vai ser uma longa leitura – faça um café primeiro.

Vamos começar.

 

A Abordagem MVC

O padrão de projeto MVC é um esforço explícito para separar três aspectos de um aplicativo de software. Neste caso, Model-View-Controller descreve a separação dos dados do aplicativo (Model) da interface do aplicativo (View) da lógica do aplicativo (Controller). Se você ainda não o fez, há um artigo explicando minha interpretação do MVC como foi implementado no pacote Flutter, mvc_pattern:

 

Um exemplo de MVC

Agora, neste artigo, os três aspectos do ‘aplicativo de amostra’ são separados ainda mais – incorporando e integrando ainda mais a própria API e estrutura do Flutter. Este aplicativo de exemplo tem seu próprio repositório Github chamado Weathercast e também está disponível no Google Play.

 

Estrutura por Design

Padrões de projeto fornecem estrutura. Eles fornecem um meio de organizar seu código. Ao fazer isso, espera-se, permite que os desenvolvedores familiarizados com o padrão de design ‘cheguem ao chão’ quando atribuídos a um projeto. Veremos como um padrão de design pode ajudar a organizar seu código, seus arquivos e até mesmo os diretórios que compõem seu projeto.

Este artigo não apenas sugerirá uma estrutura, mas também sugerirá práticas de programação específicas a serem executadas durante o desenvolvimento de um aplicativo Flutter. Observe que tudo sugerido aqui está longe de ser canônico. Eles são algo que eu faço – você pode achar útil fazer também. No final, você faz o que faz.

 

Uma estrutura de diretórios MVC

Neste aplicativo de exemplo, o padrão de design MVC é transmitido até mesmo em sua estrutura de diretórios. Observando as três capturas de tela abaixo, você está vendo a estrutura de diretórios que armazena o aplicativo de exemplo. Cada captura de tela é a anterior, mas com subdiretórios ainda mais abertos. Observe que existem alguns arquivos e até mesmo diretórios nomeados, model, view e controller. Portanto, mesmo de relance, você sabe que ‘tipo’ de código está em qual arquivo e em qual diretório.

Os diretórios de um projeto Flutter
Os diretórios de um projeto Flutter

 

Exportar MVC

Observe que nas duas últimas capturas de tela acima, os três arquivos de biblioteca, model.dart, view.dart e controller.dart são destacados com pequenas setas vermelhas. Eles agora estão listados abaixo e você descobrirá que cada um contém uma lista de instruções de exportação. Cada um representa os “três aspectos” do padrão de projeto MVC. Com isso, adivinhe o que está contido no arquivo de biblioteca, controller.dart. Olhe atentamente.

controller.dart e model.dart e view.dart
controller.dart e model.dart e view.dart

 

O arquivo de biblioteca, controller.dart, contém todo o código ‘controller’ para o aplicativo. Então, qual é a ideia por trás de tudo isso, você pergunta. Essa abordagem foi inspirada na própria documentação do Dart ao organizar um pacote de biblioteca. Os três ‘arquivos de exportação’ servem para ajudar a agilizar a fase de desenvolvimento. Eu vou explicar.

 

Tudo em seu lugar

Portanto, todo o código-fonte localizado em um diretório ‘controller’ está listado no arquivo de biblioteca, controller.dart. Todo o código-fonte considerado parte de ‘The View’ neste aplicativo é encontrado no arquivo de biblioteca, view.dart. Por fim, o arquivo model.dart contém referências a todo o código referente aos dados do aplicativo. Toda essa organização do código dessa forma é para agilizar o processo de desenvolvimento. Afinal, tempo é dinheiro.

 

Continue com isso

Como exemplo, você vê esses três arquivos listados acima agora referenciados nas instruções de importação destacadas por três setas vermelhas nas capturas de tela abaixo. Durante o desenvolvimento, a prática de fornecer automaticamente essas três instruções de importação imediatamente após criar um ‘novo arquivo Dart’ garante a você, como desenvolvedor, que terá todas as dependências necessárias para continuar com seu trabalho. Perto do final do desenvolvimento, eu incluiria a diretiva ‘show’ para referência futura (a segunda captura de tela). Com isso, semanas, meses, anos, eu ou outro desenvolvedor podemos retornar a esse arquivo de biblioteca e ver prontamente as dependências envolvidas.

Classe WeatherCon
Classe WeatherCon

A ideia é usar esses três ‘arquivos de exportação’ para reunir rapidamente as dependências necessárias com qualquer novo arquivo dart que você possa criar – basta adicionar qualquer número dos três arquivos.

 

Uma estrutura de diretórios Flutter

Observe que há um diretório chamado home na estrutura de diretórios do aplicativo. Isso informa, de relance, onde está localizado o código do widget ‘Início’. O widget ‘Home’, como você deve saber, geralmente é a tela principal do aplicativo móvel na maioria dos casos. É nomeado após a propriedade encontrada na classe, MaterialApp.

 

 

MVC dentro de MVC

Observe na última captura de tela, sob o diretório inicial, há outro conjunto de diretórios nomeados, model, view e controller, respectivamente. Todos eles pertencem ao widget ‘Home’. Adivinha o que está em cada diretório? Veja onde estou indo com isso? De relance, você pode ver qual código faz o quê.

 

Mais uma vez, tudo em seu lugar

De relance, você pode ver que há um widget de gaveta acessado no widget ‘Início’. Veja a última captura de tela acima. Veja onde o código da gaveta está localizado? Um lugar bastante lógico para isso, não? Então, onde estão os dados acessados pelo widget ‘Home’, eu me pergunto? Sob o diretório, modelo, é claro.

No início

Então, vamos recuar um pouco e perguntar onde esse Widget ‘Home’ é chamado neste aplicativo? Como na maioria dos aplicativos Flutter, você pode adivinhar que está sendo passado para a propriedade ‘home’ de algum widget MaterialApp em algum lugar. Mas onde? Veja abaixo e você verá onde.

 

O que é tudo isso?

Portanto, há a propriedade home sendo passada para o objeto de classe, Weather. Onde está esta classe, Tempo? Onde está essa classe, WeatherApp? O que é essa classe AppView que estende o WeatherApp? O que é esse método, onTheme()? E, finalmente, o que é esse con.WeatherApp passado para a propriedade, con? Muita coisa está acontecendo aqui, uh. Vamos percorrer este passo de cada vez.

Onde está Wally… quero dizer, Clima?

Em primeiro lugar, onde está esta classe, Tempo? Bem, você já tem uma boa ideia de onde é. Afinal, é o widget ‘Home’. Mais especificamente, é a tela ou a interface do widget ‘Início’. Você sabe onde está o código do widget ‘Home’ e sabe que o ‘material da interface’ para o widget ‘Home’ está no diretório chamado view. Olhando lá, você verá um arquivo de biblioteca chamado, de todas as coisas, home.dart. Aposto que está lá.

Eu tinha razão! E, ao contrário do Java, você não precisa nomear o arquivo após o nome da classe ‘principal’ no arquivo. Você pode ter qualquer número de classes, variáveis e funções de alto nível nesse arquivo! Enorme. E assim, no arquivo home.dart, encontramos a classe Weather. A primeira parte desta classe está listada abaixo.

A propósito, observe as três últimas instruções de importação acima. Você já viu esses três arquivos antes. Observe, de relance, que você pode determinar as dependências envolvidas neste arquivo de biblioteca específico. Agradável.

 

“Os Arquivos de Exportação” Um Exemplo Aplicado

Como aparte, vamos fazer um pequeno exercício demonstrando os benefícios de usar esses três arquivos de exportação. Mais uma vez, provou ser útil, pelo menos, para projetos menores. Além disso, neste pequeno exercício, demonstrarei a prática dedicada de manter as coisas consistentes. Aqui vamos nos.

Observe que havia um diretório chamado, secundário, na pasta, view. Agora, de acordo com a prática de nomear diretórios após a API do Flutter, ele deve ser nomeado filho. A segunda captura de tela é o arquivo de exportação ‘Visualizar’, view.dart. Lá, você vê os arquivos ‘view’ nesse diretório, secundário.

 

Concedido, todos nesse diretório são telas ‘secundárias’ que aparecem na tela ‘inicial’ de uma maneira ou de outra. Em muitos casos, eles são aplicados a uma propriedade ‘filho’ nessa tela ‘inicial’. Todos podem ser considerados widgets ‘filhos’ de fato. E assim, para consistência, devo renomear o diretório, filho.

Ops. Quebrou.

Agora, veja o que acontece quando eu renomeio o diretório para filho. Perdemos algumas dependências em partes do código-fonte. Faz sentido, mas depois veja com que facilidade é resolvido. Eu não tenho que ir para todos os arquivos individuais no projeto e atualizar suas instruções de importação. Eu só tenho que ir para o arquivo de exportação ‘view’ e atualizar quatro linhas, e pronto. Veja as capturas de tela abaixo.

Novamente, vale a pena? O veredicto ainda está para fora sobre este. Novamente, suspeito que esses ‘arquivos de exportação’ seriam difíceis de manter com projetos maiores. De qualquer forma! Alimento para o pensamento!

Está tudo no nome

Então vamos voltar a isso. Agora, onde está essa classe, WeatherApp? Parece ser o início deste aplicativo. Observe que o nome da classe, WeatherApp, tem a palavra ‘App’ anexada no final. Isso deve lhe dar uma pista de onde esse código pode ser encontrado. Vamos dar uma olhada na estrutura de diretórios do aplicativo de exemplo novamente. Observe que há um diretório chamado app. Aposto, você pode adivinhar o que está lá. Sim, três dire… Ops, dois diretórios! Acho que o aplicativo não precisa acessar nenhum dado. Isso é bom.

 

De novo e de novo, tudo em seu lugar

Então, onde está essa classe, WeatherApp? Vejamos as três capturas de tela abaixo. Ok, podemos descobrir isso. Faz parte da interface. Portanto, ele deve estar em um diretório chamado view. Olhando no diretório view, vemos um arquivo de biblioteca chamado weatherapp.dart. Parece promissor. Vamos abrir esse.

Isso é muito fácil. Estamos conhecendo esse aplicativo bem rápido, não estamos? Como se fosse por design! Dê uma olhada abaixo na classe, WeatherApp. Veja as duas últimas instruções de importação. São nossos velhos amigos de novo – menos o modelo. De relance, você pode ver o que está sendo usado neste arquivo de biblioteca e onde eles estão.

Observe que a diretiva ‘as’ está sendo usada na última instrução de importação. Essa é a maneira do Dart de lidar com o caso quando duas classes têm o mesmo nome — essa View e seu Controller têm o mesmo nome.

 

No início?

Então, este é o início do aplicativo? Que tal olharmos para o arquivo da biblioteca, main.dart. Por convenção, este é o início da maioria dos aplicativos Flutter porque contém a função main().

 

 

A camada de aplicativos

Então, o que você vê? Você vê uma classe chamada App sendo instanciada a partir do pacote de biblioteca mvc_application. Aqueles familiarizados com meus artigos anteriores podem reconhecer isso como parte do meu lançamento de pacote para desenvolver aplicativos Flutter.

Você também vê nossa classe, WeatherApp, que é referenciada em um dos três arquivos de ‘exportação’, view.dart. Então, qual é o método runApp() usado para iniciar este aplicativo? Sabemos que a função runApp() recebe um Widget e, portanto, devemos assumir que o objeto App é um Widget. Abaixo está uma captura de tela da classe, App e, em seguida, uma captura de tela de seu pai, AppMVC.

Sim, acontece que é um StatefulWidget. Resumindo, este objeto ‘App’ inicia as coisas usando a seguinte composição: App(View(Controller(Model()))). Observe as duas últimas setas vermelhas na segunda captura de tela. Essas funções, initApp() e init(), serão apresentadas neste artigo em breve.

E assim, seguindo esta composição, o parâmetro fornecido ao objeto App deve ser uma ‘View’ desta abordagem MVC aplicada à aplicação. Neste caso, o ‘View’ é a classe WeatherApp, que estende a classe AppView. Estamos espiando ‘olhando sob o capô’ neste momento, a propósito.

Observe que, por sua vez, um Controller é fornecido à classe ‘View’ na forma de con.WeatherApp(). Novamente, seguindo a composição, App(View(Controller(Model()))).

Controle de importação

Observe, eu não me incomodo em passar o Controller como um parâmetro dentro do arquivo main.dart. Porque se importar? Torne seu código mais modular e instancie a classe Controller dentro da View na forma de con.WeatherApp().

O controlador do aplicativo

O objetivo desta ‘camada de aplicativo’, aliás, é configurar o ‘ambiente’ para o aplicativo ser executado. O que você vê abaixo é o Controller para o aplicativo instanciado pela primeira vez na forma de con.WeatherApp().

Você pode ver que há muita coisa acontecendo aqui. Como deveria, está ‘inicializando’ o aplicativo na inicialização, afinal. Você pode ver que estende a classe, AppController. Lembra daquelas duas funções que mencionei anteriormente, initApp() e init()? Bem, eles entraram em jogo. Vê-los? Entraremos em detalhes sobre eles daqui a pouco.

 

A visão do aplicativo

Vamos voltar para a visualização do aplicativo, também chamada WeatherApp, por um momento. Ele estende a classe AppView e é instanciado primeiro no arquivo main.dart. Lembrar?

A vista sob o capô

Olhando para uma captura de tela truncada da classe, AppView. Aqui, você pode ver sua função build(). E olhe! Aí está aquele MaterialApp que estávamos procurando!

Nele, vemos o objeto da classe MaterialApp recebendo uma longa lista de parâmetros na forma de variáveis de classe e/ou funções. O que é isso tudo? Bem, como você já deve ter adivinhado, você pode ‘passar’ todas as propriedades que compõem a classe MaterialApp com essas variáveis ou funções. E com essas funções, você pode “reconstruir” dinamicamente a Árvore de Widgets com novos valores de propriedade, se desejar. Esse recurso será, de fato, demonstrado no aplicativo de exemplo.

 

Mantenha-o estático se puder

A propósito, notei enquanto escrevia este artigo que cometi um passo em falso. É sempre uma boa prática de programação manter seu código estático e imutável, se possível. Como deve haver apenas ‘uma instância’ dessas duas classes no ‘nível do aplicativo’, o uso de fábricas estáticas faz sentido e, portanto, as duas classes que acabamos de abordar foram alteradas de acordo:

Está além do escopo deste artigo, mas para acentuar o caso, aqui estão algumas referências à agora famosa publicação de Joshua Bloch: Effective Java. Claro, isso não se aplica apenas ao Java, mas é uma boa prática para qualquer linguagem de programação orientada a objetos.

  • Item 1: Considere métodos de fábrica estáticos em vez de construtores
  • Item 15: Minimizar a mutabilidade

O que tem na gaveta?

Ok, de volta a ele. Agora, há uma gaveta usada neste aplicativo. Lembrar? Como saber? Novamente, vimos isso na estrutura de diretórios sob o diretório view. Veja abaixo. Agradável.

Além disso, com essa convenção de nomenclatura imposta à estrutura de diretórios, você pode ver prontamente o que compõe a gaveta desse aplicativo de exemplo específico. Sob o diretório, gaveta, há outro diretório chamado weather_locations. Olhando dentro desse diretório, vemos nossos velhos amigos novamente: os diretórios, controller, model e view. Também vemos algo novo. Um arquivo de biblioteca chamado mvc.dart.

Olhando para a última captura de tela acima, podemos induzir que weather_locations é outra tela exibida dentro da gaveta. É uma tela porque existe um diretório chamado view. Existe um diretório chamado controller, e existe até um diretório chamado model?! Deve haver dados específicos para esta tela, weather_locations?! Excelente! Agora, o que é esse arquivo de biblioteca, mvc.dart? Vamos dar uma olhada nele abaixo.

Bem, você vai olhar para isso? Ele contém uma lista de instruções de exportação. Um olhar mais atento revela que é todo o código que compõe a tela de ‘localização do tempo’. A primeira linha contém o modelo, a segunda linha contém a visão e a terceira contém o controlador: M-V-C. Tudo em um arquivo chamado, mvc.dart. Diversão! Veja abaixo e você pode ver os arquivos da biblioteca a que se refere e um exemplo da tela ‘localização do tempo’ exibida na gaveta.

Todos os arquivos têm o mesmo nome – novamente, adiciona alguma consistência, não é? Vamos ver o que está dentro desses arquivos. Primeiro, veremos o primeiro bit do Controller:

Está tudo no nome

Observe o nome da classe Controller. Tem a abreviatura, Con, anexada no final. Os desenvolvedores reconhecerão essa classe como um Controlador. (Claro, você pode chamá-lo como quiser, mas aí está.) Observe as instruções de importação. Eles estão usando a diretiva ‘show’ para que o próximo desenvolvedor que abrir esse arquivo possa ver prontamente o que é usado e onde ele mora. Observe que a última instrução de importação é o arquivo mvc.dart. Tudo isso é uma tentativa de impor um padrão, a conformidade, um meio de ajudar o próximo desenvolvedor a “acertar no chão” quando este projeto for entregue a eles.

Vamos dar uma olhada no primeiro bit da classe Model. Como sempre, você pode clicar na captura de tela abaixo e visualizar seu código completo no Github.

As instruções de importação, de relance, informam quais classes estão envolvidas. O nome da classe informa que esta é uma classe Model. Observe que, como o Controller, ele também usa uma fábrica estática e não um construtor tradicional para instanciar a classe. Afinal, você só precisa de ‘uma instância’ dessas classes. Agora, para manter isso em andamento, como é a Visualização?

Olhando para a View, você vê que nem é uma classe, mas uma função de alto nível?! Ele retorna a variável, _demoItems, do tipo List<DemoItems<dynamic>>. Observando as importações, você vê que esta View realmente ‘conversa’ com o Controller, LocationCon e também faz referência à classe StateMVC do pacote da biblioteca ‘MVC Pattern’. Então, onde essa função de alto nível é chamada no aplicativo?

Localização! Localização! Localização!

Por causa da maneira como a estrutura de diretórios é organizada, podemos “voltar atrás” e dar um palpite sobre onde essa função de alto nível é chamada. Organizado!

Saindo da View, weather_locations.dart, com a função de alto nível, ainda estamos no diretório da gaveta. Agora, subindo um nível na estrutura de diretórios, mas ainda no diretório da gaveta, o que você vê? Se você adivinhou que é chamado no arquivo, settings_drawer.dart, você está certo.

 

Quem chama quem

A primeira captura de tela abaixo é o arquivo settings_drawer.dart. Você pode ver a chamada de função, LocationCon().listLocations(). Essa função, por sua vez, é encontrada na classe Controller, LocationCon. Agora, isso faz sentido, pois, nesse arranjo MVC, a View não ‘fala’ diretamente com o Model. Em vez disso, seu acesso à função de alto nível é por meio do Controlador. Olhando para a segunda captura de tela, você pode ver que a função de alto nível que estávamos procurando, weatherLocations, é realmente acessada no Controller.

 

Dê um passo para trás para ver a imagem maior

Vamos voltar agora e examinar um recurso específico deste aplicativo de exemplo. Com cada condição meteorológica relatada, um ‘tema de cores’ é apresentado para representar um ‘tipo’ específico de clima. Dá um pequeno toque no aplicativo.

 

Qual é o tema

Para poder alterar ‘dinamicamente’ o tema de cores do aplicativo, você precisa chamar a função setState() no objeto State que contém o tema. Em outras palavras, o objeto State que possui a classe MaterialApp em sua função build().

Neste aplicativo de exemplo, a classe ‘Theme’ é um controlador chamado ThemeCon e altera explicitamente a cor do aplicativo dependendo das condições climáticas atribuídas a um local. Ele controla a cor, se não o clima.

Então, onde está este ‘Theme Controller?’ Ele está localizado no diretório ‘Home’. Por quê? Porque é chamado pelo controlador ‘Home’. Onde está o controlador ‘Home’? Encontra-se no arquivo home.dart. Comigo até agora?

Abaixo, está o trecho de código no ‘Theme Controller’ responsável por alterar o tema de cores e uma captura de tela de onde esse arquivo está localizado.

Agora, esse ‘Theme Controller’ pode ser chamado no widget ‘home’, mas está alterando a propriedade ‘theme’ da classe MaterialApp, e já sabemos que está de volta no ‘App level’ no objeto State, AppView. E então, como você acessa o objeto State do aplicativo?— através do Controller do aplicativo, é claro.

Vamos voltar para o controlador do aplicativo, WeatherApp, rodando no ‘nível do aplicativo’. Por fim, falarei sobre essas duas funções, initApp() e init().

Você pode ver abaixo que o Controlador do aplicativo tem acesso a muitas coisas. Faça sentido – é o controlador do aplicativo! Em uma dessas duas funções ‘init’, initApp(), o objeto State do aplicativo, stateMVC, adiciona o Controller, ThemeCon. Olhe abaixo.

Em outras palavras, esse outro Controller chamado ThemeCon tem acesso ao objeto State do aplicativo — encontrado na View do aplicativo, AppView. ThemeCon agora pode chamar a função setState() no objeto State que contém o tema. Você está perdido? Vamos recuar um pouco… bem, na verdade muito!

Tudo começa sob o capô!

Vamos voltar todo o caminho para o início. Lembra do objeto ‘App’ que foi passado para a função runApp()? É um StatefulWidget, lembra? Isso significa que ele cria um objeto State. Ele cria o objeto State do aplicativo. Tanto esse StatefulWidget quanto seu objeto State chamam essas duas funções ‘init’ respectivamente. Tudo faz parte do processo de configuração do ambiente para executar seu aplicativo. Vamos dar uma olhada.

Siga os pontos de interrupção

Abaixo estão as capturas de tela do meu IDE em que a execução do aplicativo de exemplo foi pausada em pontos de interrupção específicos. A primeira captura de tela é interrompida na função initState() do objeto State do aplicativo. Veja o nome da função StatefulWidget sendo chamada lá. Ele também é chamado, initApp(). Consistência.

A segunda captura de tela foi para essa função no Statefulwidget – que você vê agora é o objeto ‘App’ passado para a função runApp(). O ponto de interrupção parou no initApp() pertencente ao controlador que foi atribuído à View do aplicativo. A propriedade, _vw, é a View do aplicativo que é a classe WeatherApp em main.dart. As coisas já estão se encaixando para você?

Não se preocupe se você estiver se perdendo. Quero dizer, se você não é do tipo que anda pelo código, por exemplo, tentando saber como os outros padrões de design funcionam como Redux, Scoped Model ou BLOC, então provavelmente você está perdendo seu tempo aqui. Acho que você é apenas um daqueles que quer fazer isso, certo? Embora, vou sugerir que há menos clichê neste. Que tal ler mais? Você chegou até aqui.

Então, agora estamos de volta à função initApp(), onde o ‘Theme Controller’ recebe acesso ao objeto State do aplicativo, stateMVC, destacado em azul. Ver?

E assim, acontece que é a função initState() do objeto State do aplicativo que, por sua vez, chama a função initApp() no Controller do aplicativo. É tudo por design.

É onde as operações síncronas devem ser realizadas. Agora que tal assíncrono?

 

Aguarde o futuro!

Com qualquer programa de computador, muitas coisas precisam acontecer antes que ele esteja pronto para o usuário… bem, usar. Há muita “inicialização” a ser feita primeiro. Este é um pequeno aplicativo de amostra simples, mas também precisa ‘se preparar’ primeiro antes de estar, bem … pronto. Vamos dar uma olhada em como este aplicativo Flutter fica pronto.

Depois que o initApp() é chamado na função initState() do objeto State do aplicativo, o objeto ‘App’ dispara sua própria função build(). Na primeira captura de tela acima, observe que o objeto ‘App’ aproveita a classe FutureBuilder. A propriedade, future, recebe uma função chamada init(). Reconhece o nome?

É assim que as operações assíncronas são tratadas. No entanto, de tal forma, que o usuário pode ter que esperar até que eles terminem. O que o usuário verá até então? O pequeno círculo giratório no centro da tela, é claro.

Na segunda captura de tela acima, você vê o que está dentro dessa função init(). Ele é interrompido na função View init() do aplicativo. Vamos dar uma olhada abaixo e ver o que está nessa função init(). Observe que estamos subindo as camadas da estrutura.

Então agora, na primeira captura de tela, vemos que estamos na View do aplicativo onde algumas coisas ‘internas’ são feitas antes de parar na função init() para a classe pai, AppViewState. A segunda captura de tela acima nos mostra nessa função init(), onde estamos prestes a entrar no Controller do aplicativo e executar sua função init(). Muita coisa acontecendo aqui sob o capô – como uma estrutura deveria.

E agora estamos de volta ao Controlador do aplicativo. Para nosso pequeno aplicativo de exemplo simples, ele se chama WeatherApp e está lá, em sua função init(), onde ele realmente chama o ‘Location Controller’ – aquele que fomos apresentados pela primeira vez no diretório, weather_locations. Lembrar? Aqui, está abrindo o banco de dados que lista os locais dessa gaveta que está no widget ‘Início’. Lembra da gaveta? A abertura de bancos de dados pode levar alguns microssegundos e, portanto, o aplicativo “espera” que isso seja feito antes de estar pronto para o usuário. Pêssego.

Há outra coisa chamada LocationTimer, listada lá também, mas vou deixar você descobrir o que é quando você baixar o aplicativo de exemplo.

De volta ao nosso tema!

Vamos voltar agora para como este aplicativo altera seu tema de cores no comando. A View do aplicativo tem uma função chamada onTheme(). Lembre-se, o uso de tais funções permite que o MaterialApp altere dinamicamente suas propriedades na próxima vez que ‘reconstruir’ sua árvore de widgets (ou seja, na próxima vez que a função setState() for chamada).

Olhando para o ‘Theme Controller’ listado abaixo, você vê que a propriedade, theme, é, na verdade, um getter. E, de fato, é a função mais abaixo chamada, weatherChanged(), que ‘reatribui’ um valor à variável, _theme.

E assim, voltando para a classe View do aplicativo, dentro do framework, há o MaterialApp que recebe uma variável ou (se essa variável for nula) uma função. Em nosso aplicativo de exemplo, utilizamos a função para atribuir dinamicamente um novo tema. Eu sugiro ‘percorrer’ o aplicativo de exemplo para ver o que quero dizer.

 

Como Funciona… Realmente

Este artigo está se transformando em um romance, eu sei. No entanto, há mais duas coisas que quero abordar aqui. Em primeiro lugar, como a estrutura funciona na execução da função principal deste pequeno aplicativo de amostra simples: Obter o clima de uma cidade. Vamos fazer um rápido ‘passeio’ disso. Se você leu a história de Felix Angelov, Weather App com “flutter_bloc”, sabe como é. Você digita o nome da cidade, ela pega o clima.

Siga o código

OK, vamos lá. No arquivo de widget ‘Home’, home.dart, há um objeto State que permite clicar em uma lupa e digitar o nome de uma cidade para obter o clima atual. Abaixo (logo acima da pequena seta vermelha) há um widget IconButton que, quando pressionado, retorna uma cidade em uma variável chamada cidade. Essa variável é passada para o ‘Controlador Meteorológico’. Veja abaixo.

Observe que o ‘Controlador Meteorológico’ tem uma função chamada onPressed(). Procuro seguir a prática de imitar a API usada no código da View para ser usada também no código do Controller. Essa coisa de consistência de novo. Vamos ver onde isso nos leva.

Na função onPressed() listada abaixo, vemos que a variável city é passada para outra função chamada getWeather(). Vemos dentro da função, getWeather(), que existe um método ‘then’. É nesse método ‘then’ que a função, rebuild(), é chamada. Finalmente, o ‘Theme Controller’ entra em cena para chamar sua função, weatherChanged(). Lembra dessa função?

Agora, se você adivinhou que as duas funções, refresh(), eventualmente chegam a chamar a função setState() de um objeto State, você está certo. Parabéns, você está começando a ver a imagem inteira.

A função refresh() do ‘Theme Controller’s chama o objeto State do AppView com seu objeto MarterialApp. Isso mudará a cor da AppBar do aplicativo. Enquanto a segunda função refresh() que você vê acima chama o objeto State do Widget ‘Home’. Isso mudará a cor de fundo da tela. Magia.

Qual é a sua preferência?
A última coisa que quero abordar rapidamente são as preferências do aplicativo. Se e quando você experimentar este aplicativo de amostra, notará que ele “lembrará” a última cidade que você escolheu e, de fato, exibirá as condições climáticas atuais na próxima vez que iniciar o aplicativo. Isso porque a ‘última cidade’ inserida é registrada nas preferências do aplicativo — com ajuda do framework.

 

Está tudo no Init

O ‘Weather Controller’, WeatherCon, irá buscar a última cidade, se houver, em sua função initState(). Lá, ele chama a função initFetch(). Abaixo está uma captura de tela truncada do controlador, WeatherCon. A seta vermelha mostra onde as preferências do aplicativo são acessadas.

Salvo para Depois

Você vê na captura de tela acima onde a ‘última cidade’ é recuperada e, de fato, onde a ‘última cidade’ é gravada — na função fetchWeather(). Lembre-se, a função, fetchWeather(), acaba sendo chamada toda vez que o usuário clica na lupa, e a função, onPressed(), é executada no código da View e depois no código do Controller.

 

As preferências

Essa classe Prefs que você vê na captura de tela acima é uma biblioteca que escrevi alguns meses depois de descobrir o Flutter. Eu precisava dessa biblioteca em meus projetos. A biblioteca Prefs na verdade faz parte do framework que estou trabalhando, mvc_application, e você já viu sendo inicializado acima, na classe AppController, quando o app estava configurando seu ambiente. Decidi publicar Prefs como um pacote de biblioteca! Eu acabei de fazer isso! Agora mesmo! Aqui está: prefs

Tem se mostrado útil em meus projetos. Vale a pena compartilhar né?! Experimente. Se você gosta, use. Se não, então não, certo?! Mais uma vez, leia o artigo acima para entender como usá-lo. Ele ainda fará parte da estrutura quando também for publicado. Sempre que isso vai acontecer?! Apenas não há tempo suficiente no dia. De qualquer forma!

 

 

Conclusão

Ok, isso foi um pouco de uma leitura. Tenho certeza que você está com fome, e ou quer continuar com sua vida. No entanto, estarei acompanhando este artigo, pois há muito mais a considerar ao desenvolver software como você sabe. Simplesmente acontece, esse framework permitirá que você faça isso, e mostrarei como em outros artigos.