Vycanis — MVP Flutter (Frontend)

Albert Eije Barreto Mouta
11 min readSep 29, 2023

--

Arquitetura e passo a passo para funcionamento do MVP

Introdução

O Vycanis Modeler (https://vycanis.top/modeler/) está preparado para gerar o MVP (Produto Viável Mínimo) para o Flutter de três formas:

  1. Persistindo dados apenas localmente usando o Drift. Neste caso, o protótipo não vai consumir um servidor REST.
  2. Consumindo um servidor REST apenas. Neste caso, o protótipo não vai persistir dados localmente.
  3. Ambas as formas. Neste caso, o protótipo estará pronto para trabalhar de uma forma ou de outra.

Como funciona a arquitetura do MVP gerado pelo Vycanis e quais os passos necessários para que o mesmo funcione? Vejamos.

Arquitetura do MVP

Antes de entrarmos no mérito de falarmos da arquitetura do MVP gerado pelo Vycanis, vamos dar uma olhada nos pacotes que são utilizados pelo MVP e estão disponíveis no arquivo pubspec.yaml.

name: vycanis-mvp
description: MVP created by Vycanis Modeler
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1

environment:
sdk: '>=3.0.3 <4.0.0'

dependencies:
flutter:
sdk: flutter
intl:
get: 5.0.0-release-candidate-4
http: ^0.13.5
drift: ^2.7.0
sqlite3_flutter_libs: ^0.5.14
path_provider: ^2.0.14
path: ^1.8.2
pluto_grid: ^7.0.2
flutter_bootstrap: any
email_validator: ^2.1.17
dropdown_button2: ^1.9.2
pdf:
printing: ^5.10.4
extended_masked_text: ^2.3.1
recase: ^4.1.0
buttons_tabbar: ^1.3.6

flex_color_scheme: ^7.0.4
simple_animations: ^4.1.0
supercharged: ^2.1.1
pin_code_fields: ^7.4.0

cupertino_icons: ^1.0.2

dev_dependencies:
build_runner: ^2.1.4
build_web_compilers: ^3.2.1
drift_dev:
flutter_test:
sdk: flutter

# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^2.0.0

flutter_icons:
android: true
ios: true
image_path: "assets/images/launcher_icon.png"

# The following section is specific to Flutter packages.
flutter:
uses-material-design: true
assets:
- assets/images/
- assets/fonts/
- assets/fonts/report/

Logo abaixo temos um breve resumo do que fazem os principais pacotes utilizados:

  • GetX: GetX é uma solução extraleve e poderosa para Flutter. Ele combina gerenciamento de estado de alto desempenho, injeção inteligente de dependência e gerenciamento de rotas de forma rápida e prática.
  • http: pacote para fazer requisições HTTP.
  • Drift: Drift é uma biblioteca de banco de dados poderosa para aplicativos Dart e Flutter. Ele nos permite persistir os dados localmente no SQLite para aplicações desktop e mobile e utilizar o Local Storage para persistir os dados localmente no navegador.
  • sqlite3_flutter_libs: Utilizado para persistir os dados localmente no banco de dados SQLite.
  • pluto_grid: PlutoGrid is a DataGrid that can be operated with the keyboard in various situations such as moving cells.
  • Flutter Bootstrap: Pacote utilizado para facilitar a criação de telas responsivas.
  • Email Validator: Pacote utilizado para validar e-mails.
  • Priting: Pacote utilizado para criar relatórios incríveis com os Widgets com opção de impressão e compartilhamento de arquivo PDF.
  • extended_masked_text: Pacote utilizado para criar widget de input com máscaras definidas pelo desenvolvedor.
  • recase: Pacote que facilita a manipulação e conversão de strings.
  • buttons_tabbar: Pacote que fornece uma barra de guias onde cada aba é um botão de alternância.
  • FlexColorScheme: Pacote para a criação de temas.

GetX

Nós usamos o GetX no projeto que é gerado pelo Vycanis. E o que é o GetX? O texto a seguir foi retirado do site do projeto:

O GetX é uma solução extra leve e poderosa para o Flutter. Ele combina gerenciamento de estado de alto desempenho, injeção de dependência inteligente e gerenciamento de rotas de forma rápida e prática.

O GetX tem 3 princípios básicos. Isso significa que esses são a prioridade para todos os recursos na biblioteca: PRODUTIVIDADE, DESEMPENHO E ORGANIZAÇÃO.

DESEMPENHO: O GetX está focado no desempenho e no mínimo consumo de recursos. O GetX não utiliza Streams ou ChangeNotifier.

PRODUTIVIDADE: GetX usa uma sintaxe fácil e agradável. Não importa o que você queira fazer, sempre há uma maneira mais fácil com GetX. Isso economizará horas de desenvolvimento e fornecerá o máximo desempenho que sua aplicação pode oferecer.

ORGANIZAÇÃO: GetX permite o desacoplamento total da Visualização, lógica de apresentação, lógica de negócios, injeção de dependência e navegação. Você não precisa do contexto para navegar entre rotas, portanto, não depende da árvore de widgets (visualização) para isso. Você não precisa do contexto para acessar seus controladores/blocos por meio de um InheritedWidget, portanto, você desacopla completamente sua lógica de apresentação e lógica de negócios de sua camada de visualização. Você não precisa injetar suas classes de Controladores/Modelos/Blocos em sua árvore de widgets por meio de MultiProviders. Para isso, o GetX usa seu próprio recurso de injeção de dependência, desacoplando completamente a DI de sua visualização.

Com o GetX, você sabe onde encontrar cada recurso de sua aplicação, tendo código limpo por padrão. Além de facilitar a manutenção, isso torna o compartilhamento de módulos algo que até então no Flutter era impensável, algo totalmente possível. GetX é a maneira mais fácil, prática e escalável de criar aplicativos de alto desempenho com o Flutter. Tem um grande ecossistema ao seu redor que funciona perfeitamente junto, é fácil para iniciantes e preciso para especialistas. É seguro, estável, atualizado e oferece uma enorme variedade de APIs integradas que não estão presentes no SDK Flutter padrão.

GetX possui um grande ecossistema, uma grande comunidade, um grande número de colaboradores e será mantido enquanto o Flutter existir. GetX também é capaz de rodar com o mesmo código no Android, iOS, Web, Mac, Linux, Windows e em seu servidor. É possível reutilizar totalmente seu código feito no frontend em seu backend com o Get Server.

Para se aprofundar no uso do GetX você pode ler a documentação nos seguintes links:

Estrutura

Na imagem abaixo podemos ver a estrutura de pastas do projeto.

Estrutura de pastas do projeto gerado pelo Vycanis

Vamos explicar agora como chegamos nessa arquitetura. Explicamos as pastas na ordem que aparecem acima.

  • bindings: A classe Binding é uma classe que vai desacoplar a injeção de dependência, enquanto liga as rotas ao gerenciador de estados e o gerenciador de dependências.
  • controller: Nossos controllers, que são os responsáveis pelas regras de negócio e alterações de estado. Também é onde criaremos nossos observáveis com seus respectivos estados iniciais e os eventos que serão responsáveis por alterar esses estados.
  • data: É apenas uma pasta responsável por guardar TUDO relacionado aos dados. É nessa pasta que colocamos as pastas: domain, model, repository, e provider.
  • domain: Classes de domínio que ‘traduzem’ alguns dados para o usuário de tal forma que fique mais fácil realizar a leitura. Por exemplo, estamos guardando no banco de dados “F” para Pessoa Física e “J” para Pessoa Jurídica. Mas exibir apenas “F” ou “J” na tela, num relatório, etc, não seria muito interessante. A classe de Domain vai ‘traduzir’ o “F” para “Física” e o “J” para “Jurídica” e, na hora de gravar no banco de dados ou de enviar os dados para o servidor via JSON, vai fazer a tradução inversa.
  • model: Pasta que armazena as classes de modelo.
  • provider: Pasta responsável por agrupar nossos provedores de dados, pode ser tanto um banco de dados ou uma api. No caso do projeto gerado pelo Vycanis podemos ter as pastas ‘api’ e/ou ‘drift’ dentro dessa pasta.
  • api: Essa pasta fica dentro da pasta provider e armazena as classes que são utilizadas para consumir a API REST.
  • drift: Essa pasta fica dentro da pasta provider e armazena todas as classes necessárias para armazenar os dados localmente no SQLite.
  • repository: Ponto único de acesso aos dados.
  • infra: Classes de infraestrutrura.
  • page: Armazena as páginas da aplicação.
  • grid_columns: As colunas que são desenhadas nas PlutoGrid de cada página.
  • login: Widgets que formam a página de login.
  • modules: Para cada página (janela) gerada é criada uma pasta dentro de modules. No exemplo em questão temos 5 pastas, o que significa que essa aplicação tem 5 janelas.
O acesso às páginas (janelas) criadas é feito pelo menu lateral.
  • shared_page: Páginas padrões que são compartilhadas e utilizadas pelas demais páginas: Filtro, Lookup, Report, Splash Screen.
  • shared_widget: Conjunto de widgets e classes compartilhados por todas as páginas.
  • routes: Contém as classes reponsáveis pelas rotas.
  • translations: Contém os arquivos de tradução. Por padrão o MVP é gerado com as traduções para inglês, português e espanhol.

Além das pastas acima, com o tempo incluímos também uma pasta chamada ‘mixin’. Ela se encontra dentro da pasta “lib\app”, conforme imagem a abaixo.

Nova pasta mixin

E para que serve essa pasta? Ela contém um arquivo chamado ‘controller_base_mixin.dart’. O objetivo desse arquivo é trazer uma estrutura mínima para controle de acessos. Vamos dar uma olhada em seu código.

mixin ControllerBaseMixin {
bool canInsert = false;
bool canUpdate = false;
bool canDelete = false;
String functionName = "";

void noPrivilegeMessage() {
showNoPrivilegeSnackBar();
}

void setPrivilege() {
canInsert = Session.loggedInUser.administrador == 'S'
? true
: Session.accessControlList.where( ((t) => t.funcaoNome?.toLowerCase() == functionName.toLowerCase()) ).toList().isNotEmpty
? Session.accessControlList.where( ((t) => t.funcaoNome?.toLowerCase() == functionName) ).toList()[0].podeInserir == 'S'
: false;
canUpdate = Session.loggedInUser.administrador == 'S'
? true
: Session.accessControlList.where( ((t) => t.funcaoNome?.toLowerCase() == functionName.toLowerCase()) ).toList().isNotEmpty
? Session.accessControlList.where( ((t) => t.funcaoNome?.toLowerCase() == functionName.toLowerCase()) ).toList()[0].podeAlterar == 'S'
: false;
canDelete = Session.loggedInUser.administrador == 'S'
? true
: Session.accessControlList.where( ((t) => t.funcaoNome?.toLowerCase() == functionName.toLowerCase()) ).toList().isNotEmpty
? Session.accessControlList.where( ((t) => t.funcaoNome?.toLowerCase() == functionName.toLowerCase()) ).toList()[0].podeExcluir == 'S'
: false;
}

}

Observe que temos as variáveis canInsert, canDelete e canUpdate. Além disso, temos a variável functionName e o método setPrivilege.

O funcionamento é simples. É preciso cadastrar no banco de dados todas as funções do sistema de acordo com as tabelas do banco de dados. Por exemplo, se existe uma tela para cadastro de produto e essa tela armazena os dados na tabela PRODUTO, então a função que será cadastrada no banco de dado será ‘PRODUTO’. Nessa tabela de funções deve ser gravado ainda o identificador do usuário, ou seja, essa tabela de funções deve ser vinculada ao usuário, e deve ter ainda os campos PODE_ACESSAR, PODE_INSERIR, PODE_ALTERAR e PODE_EXCLUIR. Dessa maneira, para cada tela do sistema você saberá se determinado usuário poderá entrar na tela, inserir dados, alterar dados e excluir dados.

Existe um arquivo chamado session.dart que vai armazenar o usuário e a lista de funções. Esse arquivo, inclusive, contém um usuário ‘fake’ como exemplo, que já vem com a função de administrador configurada como ‘S’, ou seja, o MVP estará com todas as funções liberadas quando for executado pela primeira vez até que o desenvolvedor implemente o controle de acessos.

Em termos de controller, cada controller que for vinculado a uma tela principal deverá usar o mixin ‘ControllerBaseMixin’. O controller deverá informar qual é o nome da função, que será setado na variável ‘functionName’ e deverá ainda chamar o método ‘setPrivilege()’, tudo isso dentro do seu onInit(). Além disso, cada tela que contém a grid com os dados (as chamadas ListPage) das telas principais deverão verificar se o usuário possui acesso para incluir, alterar e excluir dados.

Finalizando, o ‘drawer’, que é o menu suspenso fornece acesso às telas da aplicação deve também conter um código que verifica se o usuário é administrador ou se ele tem acesso para entrar naquela tela.

Botando pra Rodar

Botar o MVP Flutter gerado pelo Vycanis é muito fácil. Assista ao vídeo abaixo para ver o passo a passo.

Como visto no vídeo, o processo é bem simples. O projeto já vem completo. Você realizará os seguintes passos:

  1. Crie um novo projeto Flutter com o mesmo nome do projeto que você gerou. O nome deve ser o mesmo que se encontra na primeira linha do arquivo pubspec.yaml.
  2. Copie todo o conteúdo da pasta do projeto gerado pelo Vycanis para a pasta do projeto que você criou.
  3. Abra um prompt, entre na pasta do projeto criado por você e chame os comandos: ‘flutter clean’ e ‘flutter pub get’.
  4. Execute o programa ‘generate_js.sh’. Ele fará todo o procedimento necessário para a criação das classes do Drift para que a aplicação persista dados localmente, inclusive em navegadores. OBS: é necessário ter o GIT instalado no computador. Caso ocorra algum erro neste passo, certifique-se de ter o GIT instalado.
  5. Execute a aplicação com o comando ‘flutter run’ ou abra-a usando o VS Code. Observe no vídeo que ao executar a aplicação para web houve um problema. Ocorre que o arquivo web já está pronto para que a aplicação seja publicada num servidor web. Para executá-la em modo de projeto é preciso alterar o arquivo ‘index.html’ conforme demonstrado no vídeo.

Três Tipo de MVP Flutter

O Vycanis gera o MVP Flutter de três maneiras diferentes:

  1. Flutter Local and Remote: O MVP gerado poderá persistir os dados localmente no SQLite e também poderá consumir uma API REST (normalmente também gerada pelo Vycanis). A única coisa que precisa ser feita para alternar entre persistência local e consumo de API remoto é alterar uma linha no arquivo constants.dart: no caso da imagem a seguir, podemos ver na linha 20 a variável usingLocalDatabase (usando banco de dados local). Se for true, estará persistindo dados localmente, se for false, estará consumindo uma API REST.
Alterando o valor da variável usingLocalDatabase é possível alternar entre local e remoto

2. Flutter Local Only: O MVP gerado vai persistir os dados apenas localmente. A pasta ‘api’ que normalmente se encontra dentro da pasta “\data\provider” não será criada. Não haverá necessidade de alterar a variável mencionada no passo 1, pois os dados serão persistidos apenas localmente no SQLite ou no localstorage do navegador. Todos os passos vistos no vídeo serão necessários.

3. Flutter Remote Only: O MVP gerado vai consumir uma API REST (normalmente gerada também pelo Vycanis). Não haverá a possibilidade de persistir dados localmente no SQLite. Não haverá necessidade de alterar a variável mencionada no passo 1, pois o sistema estará consumindo uma API REST e os dados estarão sendo persistidos num servidor remoto. Neste caso não é preciso efetuar o passo 4 visto no vídeo anterior, pois não há necessidade de criar classes para o Drift. A pasta ‘drift’ que normalmente se encontra dentro da pasta “\data\provider” não será criada.

Qual Versão do Flutter?

Dentro do arquivo pubspec.yaml você vai encontrar um comentário com as versões que estão sendo utilizadas no momento pelo Vycanis. Esse comentário nada mais é do que o resultado do comando flutter doctor. Observe a seguir.

# Flutter Doctor from Vycanis Machine 
#Doctor summary (to see all details, run flutter doctor -v):
#[√] Flutter (Channel stable, 3.16.5, on Microsoft Windows [versÆo 10.0.19045.3803], locale pt-BR)
#[√] Windows Version (Installed version of Windows is version 10 or higher)
#[√] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
#[√] Chrome - develop for the web
#[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.5.5)
#[√] Android Studio (version 2021.2)
#[√] VS Code (version 1.85.1)
#[√] Connected device (3 available)
#[√] Network resources

Para garantir que tudo funcione de acordo, instale a mesma versão do Flutter no seu computador.

--

--

Albert Eije Barreto Mouta

Meu lema é: você pode tanto quanto sabe. Desenvolvo e ensino a desenvolver software.