O conjunto “Controller” e “GetBuilder” permite que a aplicação se torne reativa, ou seja, ele pode atualizar um dado na página do aplicativo.

Em um primeiro momento quando comparado com “ValueNotifier“, bem simplista, o ganho é quase inexistente. Assim como “ValueNotifier” que exige instânciar o objeto e estabelecer o seu gerenciador de estado “ValueListenableBuilder“, em um primeiro olhar (incorreto), o mesmo ocorria com seus equivalentes “Controller” e “GetBuilder”, vejamos o exemplo:

Criando o “Controller”:

class Controller extends GetController {
  int value;
  Controller(this.value) {
    print('Controller $value foi criado');
  }
  inc([int v = 1]) {
    value += v;
    update();
  }

  dec([int v = 1]) {
    inc(-v);
  }
}

Usando o “GetBuilder”:

Widget build(BuildContext context) {
    return ContainerGroup(
      title: 'GetBuilder',
      width: 150,
      child: GetBuilder<Controller>(
          // init: Controller(0),   // transferido para o binding (Setup() na carga no main.dart)
          builder: (a) => Container(
                alignment: Alignment.center,
                width: 140,
                child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: [
                      Button(
                          width: 40,
                          child: Text('+'),
                          onPressed: () => a.inc()),
                      Text('${a.value}'),
                      Button(
                          width: 40,
                          child: Text('-'),
                          onPressed: () => a.dec()),
                    ]),
              )),
    );
  }

onInit / onClose – Controller

O “Controller” ganho dois eventos que ser sobrescrito (@override) permindo uma separação do código um pouco mais agrupado. onInit é chamado ao criar o GetBuilder (que é um StatefulWidget) equando que onClose é chamado ao ser encerrado.

Os eventos estão encapsulados no “initState” e “dispose” de um StatefulWidget/State, ou seja, então aqueles códigos de inicialização e finalização podem ser transferidos para o “Controller”, entregando mais separação de código ao controller melhorando a leitura e manutenção.

GetBuilder turbinado – sem instanciar o controller

É aqui que o GetBuilder ganha força, NÃO requer uma instância do controller (requer somente na primeira chamada).

Neste ponto simplifica o uso de “controllers” que são utilizados em diferentes classes; o parâmetro “global:true” permite utilizar ou compartilhar a mesma instância do objeto, funcionando como um singleton.

O “GetBuilder” reaproveita o “controller” da primeira chamada armazenada em uma pilha “global”, reutilizando-o nas próximas chamadas.

Como agora a instância é global é bem vindo a propriedade “autoRemove” do “GetBuilder” instruindo a destruição da instância criada sem a necessidade de uma chamada direta ao “dispose”.

https://pub.dev/packages/get

ForEach faz uso de funções anônimas para realizar a tarefa.

base
Funções e/ou Procedures anônimas são úteis para gerar mecanismo de eventos sem necessidade de criar um evento para objeto. O uso de um evento é obrigatório a existência de uma classe para injetar o evento a ser chamado. Com anônimos é possível injetar um evento a qualquer chamada, não demandando a obrigação da criação de uma classe.

elementar meu caro
Um anônimo pode figurar como um “procedure” (sem retorno) ou uma “function” (com um valor de retorno). Usar como procedure ou função é dependente do objetivo que o CODER pretende empregar ao seu código, se deseja um valor de retorno a resposta será usar um função nas demais irá preferir usar um procedure.

prática

  • Unit: MVCBr.ApplicationController
    ForEach(….): executa um loop sobre uma lista de itens enquanto existir item ou a Função Anônima retornar FALSE;
    Exemplo:
    [code]
    function TApplicationController.FindModel(AGuid: TGuid): IModel;
    var
    rst: IModel;
    begin
    rst := nil;
    ForEach(
    function(ACtrl: IController): boolean
    begin
    result := false;
    ACtrl.GetModel(AGuid, rst);
    if assigned(rst) then
    result := true;
    end);
    result := rst;
    end;
    [/code]
  • Unit: Data.DB.Helper
    ForEach em um objeto TDataset:
    [code]
    ForEach(function (ds:TDataset):boolean
    begin
    /// executa para cada item do dataset
    result:= false; /// não finalizar o loop e ir para o proximo
    end);
    [/code]

  1. Como inicializa o projeto  MVCBr ?

    R. No início no projeto (.dpr) o assistente inclui:
    [code]
    ApplicationController.Run(TLojaAppController.New,
    function: boolean
    begin
    // retornar True se o applicatio pode ser carregado
    // False se não foi autorizado inicialização
    result := true;
    end);
    [/code]

  2. No formulário principal, como abrir uma janela com outra VIEW?

    R. todo VIEW representa um formulário e é controlado por um “controller” – o “controller” é a porta de entrada para inicializar um formulário, assim é necessário criar o “controller”:
    [code]
    procedure TFormChildSampleView.Button1Click(Sender: TObject);
    var aController:IFormChildController;
    begin
    aController := resolveController<IFormChildController>;
    aController.ShowView();
    end;
    [/code]

  3. Tenho um MODEL com regras de negócio, como acessar os dados desses MODEL?

    R. O primeiro passo é tornar o MODEL visível para o “controller”, visto que o “controller” mantem uma lista de MODEL associado a ele mesmo.
    Para instânciar o MODEL dentro do “controller”:
    [code]
    /// Adicionar os modulos e MODELs personalizados
    Procedure TFormChildSampleController.CreateModules;
    begin
    // adicionar os seus MODELs aqui
    // exemplo: add( MeuModel.new(self) );

    add( TRegrasNegociosModel.new(self) );
    end;
    [/code]
    Na view você pode usar o “controller” para chamar o “MODEL”.

  4. Como chamar um MODEL de dentro de um VIEW?

    R. O MODEL é inicializado junto com o “controller” que mantem uma lista de MODELs ativos para acesso.
    [code]
    procedure TFormChildSampleView.Button2Click(Sender: TObject);
    var
    AModel:IRegrasNegociosModel;
    begin
    AModel := GetModel<IRegrasNegociosModel>;
    AModel.Validar(‘Show Model Validar()’);
    end;
    [/code]

  5. Quero embutir um VIEW dentro de um TABSHEET, como posso acessar os atributos do formulário ?

    R. você precisa inicialmente carregar o “controller” do VIEW – isto vai permitir acesso ao GETVIEW.This que retorna a instância do TFORM;
    [code]
    procedure TFormChildSampleView.Init;
    var
    ACtrl:IEmbededFormController;
    AForm:TForm;
    begin
    // incluir incializações aqui

    // embeded form…
    ACtrl := resolveController<IEmbededFormController>;
    AForm := TForm(ACtrl.GetView.This);
    AForm.parent := TabSheet1;
    AForm.BorderStyle := bsNone;
    AForm.Align := alClient;
    AForm.Show;

    end;

    [/code]

  6. Quando trabalho com FMX é comum utilizar TABCONTROL ou TLAYOUT para mostrar VIEWs, como um VIEW pode enviar uma mensagem para outro VIEW que já esta carregada ?

    R. para enviar uma mensagem para um VIEW específico, você precisa da “interface” do VIEW desejado – o que pode ser feito incluindo no USES da UNIT uma chamada para a UNIT de “interface” do VIEW.
    [code]
    procedure TFormChildSampleView.Button4Click(Sender: TObject);
    begin
    ApplicationController.ViewEvent(IEmbededFormView,’Message to: EmbededFormView’);
    end;
    [/code]
    Outra forma é se você conhece o “controller” alvo, pode enviar a mensagem diretamente para o “controller”.
    [code]
    procedure TFormChildSampleView.Button5Click(Sender: TObject);
    begin
    ResolveController<IEmbededFormController>.ViewEvent(‘Message via controller’);
    end;
    [/code]
    Para mandar mensagem para todos os VIEW é possível utilizar o ApplicationController:
    [code]
    procedure TFormChildSampleView.Button3Click(Sender: TObject);
    begin
    applicationController.ViewEvent( ‘generic message to all VIEW’ );
    end;
    [/code]

    GIT com Exemplo

Tenho visto muitos videos que falam sobre como aplicar MVC com Delphi e sempre fiquei com a impressão que não é nada fácil. O que não é fácil se torna um desafio – é assim que nasceu MVCBr – Um desafio para elevar a organização do código a outro patamar.

A filosofia nativa vendida pelo fabricando do delphi, basta ver os diversos videos disponíveis na comunidade, é que a ferramenta suporta MVVM, ou seja Model View e View Model – talvez seja este o maior entrave em implementar um MVC.

1) O desafio

Sendo a IDE altamente produtiva é preciso que um projeto MVC não retire aquilo que há de melhor, mesmo assim, encontramos muitos que condenam a condição de “arrastar e soltar” utilizada deliberadamente ao longo dos anos, formando uma legião de arrastadores de componentes para a interface e quando precisa de um pouco mais de profundidade da engenharia de software – foi esquecida.

2) Onde encaixar o Controller (“o bobo da corte”)

O papel o Controller é fazer o meio de campo entre o VIEW e MODEL, o que já chamei antes de “bobo da corte”, ou seja, é o controle que faz a ponte entre os MODELs e as VIEWs. Quando uma VIEW precisa de alguma informação, ela pregunta para o CONTROLLER quem detém este dado – em existindo entrega a VIEW o MODEL que é responsável pelo informação.

3) A View – camanda de apresentação

É a VIEW que se encarrega de fazer a apresentação ao usuário – no caso de desktop o “FORM” – toda VIEW possui um controller associado a ela. Se o aplicativo rodar em um browser, a VIEW é a página de apresentação para o usuário…

Não é responsabilidade da VIEW saber como guardar informações;

4) O Model – onde tudo acontece

É no MODEL que as regras acontecem. Persistência, cálculos, DAO, ORM…

VIEWMODEL

No MVCBr ainda temos a figura do VIEWMODEL – o ViewModel é um artefato de MVVM e não de MVC, no entanto o seu uso pode ser bastante útil para não nos afastarmos muito da filosofia da ferramenta.

O que fazer com o VIEWMODEL ?

Com o ViewModel, pode-se adotar uma estrutura híbrida e delegar ao VIEWMODEL a validação do formulário. Sendo ele ao mesmo tempo um conector direto para o VIEW com funcionalidades de um MODEL, ou seja, ele pode receber validações do FORM ou mesmo guardar alguns estados ligados ao FORM (ou particulares ao VIEW).

 

O MVCBr

No MVCBr, a relação entre CONTROLLER  e  VIEW  é de 1 para  <=1 – Um Controller possui  no máximo  uma  VIEW, em alguns casos talvez nenhuma. Assim toda vez que se cria uma VIEW é preciso construir um controller para a VIEW.

Já o CONTROLLER pode possuir ligações para múltiplos MODELs do projeto. Sendo o MODEL um artefato que possui regras de negócio, um MODEL pode conter as regras de persistência de cliente enquanto outro MODEL possui regras de transação de cartão de crédito separando cada MODEL por finalidade.

Se um MODEL implementar uma NFe, ele dever saber lidar com o acesso ao cadastro de cliente, acessar cadastro de produto …. além de fazer persistência do conjunto de regras de NFe – Muitas vezes um MODEL poderá precisar de ajuda de outro MODEL;

O Assistente do MVCBr

Desafio lançado – código complexo. Precisamos de um assistente para nos ajudar a escrever código. Foi pensando nisto que o Assistente do MVCBr esta apto a criar o projeto com estrutura de MVC, VIEW, MODEL e CONTROLLER para a nossa aplicação;

Todo o projeto tem por base trabalhar com assinaturas publicadas em INTERFACEs visando eliminar ao máximo o acoplamento, implementadas em UNITs em separado dos seus “Object Factory”. Neste contexto, sempre que uma UNIT precisa de um recurso que se encontra em outra UNIT – deverá preferencialmente trocar informações pelas INTERFACEs.

Todo “Object Factory” public o seu THIS, uma espécie de SELF para o “Factory” onde permite facilmente fazer CAST de interfaces usando   “This. AsType<interface>” – claro que o “Factory” precisa conhecer e implementar a tal interface solicitada (ou retornará NIL).

 

Veja o Vídeo:  Criando um Projeto com FireDAC

Códigos:  MVCBr