News & Events
Tutorial: Criando meu app step-by-step – ArrayDML – Parte VIII

Estamos entrando na reta final de nosso Tutorial: Criando meu app step-by-step, e na parte VII iniciamos uma das partes cruciais de nosso curso: O Sincronismo. Vimos como usar a classe TFDJSONDataSets para exportar dados do servidor DataSnap para nosso aplicativo Mobile. Mas não vimos como fazer com que os dados recebidos sejam inseridos no banco de dados local SQLite, veremos então como utilizar comandos ArrayDML para fazer rapidamente essas inclusões.
Na parte VII então vimos os seguintes tópicos:
- Criação da tabela do banco de dados no Firebird;
- Criação dos métodos de exportação de Títulos no lado servidor;
- Leitura e atualização da tabela Títulos local no dispositivo móvel;
Como mencionado, veremos aqui os seguintes recursos:
- Comandos ArrayDML para rápida inserção no banco;
- Ajustes na liberação de memória de objetos FORM;
Requisitos:
Tutorial: Criando meu app step-by-step – ArrayDML – Parte VIII
A grande maioria dos desenvolvedores Delphi que conheço tem como costume usar ClientDataSet e comandos do tipo Append e Post para criar rotinas de importação de dados em seus sistemas, isso porque é mais prático e rápido o desenvolvimento de tais rotinas. Eu mesmo já cansei de criar ferramentas de importação usando esse método. Entretanto, sabemos que é muito mais rápido fazer a importação usando comandos (instruções) SQL diretamente no banco, haja vista que o banco processa as instruções com maior velocidade. Para os leigos, a explicação é simples.
Para o uso de Appends e Posts (com aplicações multi-camadas), normalmente é necessário usar uma estrutura semelhante a Figura 1.
Figura 1. Estrutura básica de importação de dados
Note que vemos uma estrutura básica usando os quatro componentes base do DBExpress (existem milhares de outras estruturas de componentes) onde normalmente nossas telas, a parte visual do sistema, se conecta ao ClientDataSet1 que poderia ser por exemplo uma janela de clientes. O ClientDataSet Auxiliar (cdsAuxiliar) seria usado para “rodar” um loop em dados temporário fazendo a inclusão em ClientDataSet1, algo como a Listagem 1.
procedure TForm1.Importar; begin DataModule.cdsAuxiliar.First; while not DataModule.cdsAuxiliar.EOF do begin DataModule.ClientDataSet1.Append; DataModule.ClientDataSet1.FieldByName('CAMPO1').AsString := DataModule.cdsAuxiliar.FieldByName('CAMPO1').AsString; DataModule.ClientDataSet1.FieldByName('CAMPO2').AsString := DataModule.cdsAuxiliar.FieldByName('CAMPO3').AsString; DataModule.ClientDataSet1.FieldByName('CAMPO3').AsString := DataModule.cdsAuxiliar.FieldByName('CAMPO3').AsString; DataModule.ClientDataSet1.Post; DataModule.cdsAuxiliar.Next; end; DataModule.ClientDataSet1.ApplyUpdates(-1); end;
Listagem 1. Estrutura básica de importação
Essa estrutura é relativamente lenta em função de um fator bastante simples de entender. Quando chamamos o método Post do DataSet (nesse caso um ClientDataSet) os dados são gravamos em memória, utilizando a memória do computador que está executando o método. Ao final do nosso loop, fazemos então um ApplyUpdates, que informa ao ClientDataSet que os dados devem ser empacotados e enviados ao o DataSetProvider. Todo esse processo exige tempo, por isso a lentidão.
Podemos executar comandos SQL diretamente no SQLConnection que nesse caso já nos dará um ganho de performance de cerca de 30%, ou seja, 30% mais rápida a inclusão no banco.
Mas creio que ficarão ainda mais surpresos quando disser que o método utlizado com FireDAC e ArrayDML nos dará um ganho de perfomance de até 60%. Isso mesmo! Nos meus testes consegui aumentar em 60% a velocidade de importação de dados.
Entendendo o conceito
A estratégia de uso do FireDAC com ArrayDML para importação de dados ou simplesmente para qualquer inserção de dados direto no banco é bem simples e de rápido entendimento. Vejamos.
No FireDAC, mais precisamente no FDQuery, temos o comando Execute assim como outros DataSets, mas nesse caso podemos passar um número de inserções a ele e fazer o preenchimento de todos os parâmetros da query de uma só vez. Em nosso exemplo anterior, na parte VII de nosso tutorial, iniciamos a sincronização usando o algoritmo da Listagem 2.
procedure TfrmMain.spbSincronizarClick(Sender: TObject); var LDataSetList : TFDJSONDataSets; begin MessageDlg('Confirma sincronismo de dados?', TMsgDlgType.mtConfirmation, [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo], 0, procedure (const AResult: TModalResult) begin //Efetua o download da tabela TITULOS vinda do Servidor DataSnap LDataSetList := ModuloCliente.SrvServerMetodosClient.GetTitulos; //Prepara o MemoryTable temporário memTemporario.Active := False; //Fazemos um teste para verifica se realmente há DataSet no retorno da função Assert(TFDJSONDataSetsReader.GetListCount(LDataSetList) = 1); //Adicionamos o conteúdo do DataSet "baixado" ao Memory Table memTemporario.AppendData(TFDJSONDataSetsReader.GetListValue(LDataSetList, 0)); end ); end;
Listagem 2. Algoritimo de sincronismo parte VII
Repare que paramos na parte que recebemos do servidor DataSnap os dados para importação e deixamos um MemoryTable preparado para isso: memTemporario. Ele possui os registros da tabela TITULOS vinda da base Firebird no servidor DataSnap. O que precisamos fazer aqui é percorrer esse MemoryTable e então incluir cada registro da tabela em nossa tabela TITULOS no SQLite local.
Crie então um novo procedimento chamado AtualizarTitulos recebendo um único parâmetro do tipo TFDMemTable, como na linha abaixo:
procedure AtualizarTitulos(AMemoryTable: TFDMemTable);
Receberemos nesse método a tabela que contém os dados a serem importados, ou seja, nosso memTemporario. Pressione Ctrl + Shift + C para que o Delphi crie o escopo de nosso método e vamos a codificação. Digite o código da Listagem 3.
procedure TfrmMain.AtualizarTitulos(AMemoryTable: TFDMemTable); const _INSERT = 'INSERT INTO TITULOS ' + '( ' + ' ID_TITULO , ' + ' TITULO , ' + ' SUBTITULO , ' + ' FOTO , ' + ' MINIATURA ' + ') ' + 'VALUES ' + '( ' + ' :ID_TITULO , ' + ' :TITULO , ' + ' :SUBTITULO , ' + ' :FOTO , ' + ' :MINIATURA ' + '); '; var intNumInserts : Integer; begin {Número de registros recebidos do servidor} intNumInserts := AMemoryTable.RecordCount; DM.qryAuxiliar1.Active := False; DM.qryAuxiliar1.SQL.Text := _INSERT; {Informamos a Query Auxliar qual o número de Inserts será efetuado} DM.qryAuxiliar1.Params.ArraySize := intNumInserts; AMemoryTable.First; while not AMemoryTable.Eof do begin DM.qryAuxiliar1.ParamByName('ID_TITULO').AsIntegers[AMemoryTable.RecNo-1] := AMemoryTable.FieldByName('ID_TITULO').AsInteger; DM.qryAuxiliar1.ParamByName('TITULO') .AsStrings [AMemoryTable.RecNo-1] := AMemoryTable.FieldByName('TITULO') .AsString; DM.qryAuxiliar1.ParamByName('SUBTITULO').AsStrings [AMemoryTable.RecNo-1] := AMemoryTable.FieldByName('SUBTITULO').AsString; DM.qryAuxiliar1.ParamByName('FOTO') .AsBlobs [AMemoryTable.RecNo-1] := AMemoryTable.FieldByName('FOTO') .AsVariant; DM.qryAuxiliar1.ParamByName('MINIATURA').AsBlobs [AMemoryTable.RecNo-1] := AMemoryTable.FieldByName('MINIATURA').AsVariant; AMemoryTable.Next; end; {Se houver mais de um registro a ser aplicado, então chama o Execute da Quuery Auxiliar} if intNumInserts > 0 then DM.qryAuxiliar1.Execute(intNumInserts, 0); DM.qryAuxiliar1.Active := False; end;
Listagem 3. Código de sincronismo atualizado
A princípio parece longo o código e talvez confuso, mas acalme-se porque é fácil. Logo de início criamos uma constante chamada _INSERT que receberá a instrução SQL INSERT para executarmos no servidor (nesse caso local). Não entrarei em detalhes, sobre os comandos.
Em seguida declaramos uma variável que receberá a quantidade de registros presente no MemoryTable, o RecordCount. Isso será usado para alimentar a subpropriedade ArraySize de Params. O que queremos dizer aqui é que teremos um array de conteúdos de campos para a LISTA de parâmetros de cada registro. Preenchemos esse array com o conteúdo de cada campo e por último executamos.
Repare que efetuamos um Loop do MemoryTable e preenchemos cada parâmetro usando seu respectivo tipo de dados no plural (O plural foi apenas para chamar a atenção do leitor), veja:
DM.qryAuxiliar1.ParamByName('ID_TITULO').AsIntegers[AMemoryTable.RecNo-1] :=
Nos colchetes passamos o RecNo -1 (o array começa em 0 (zero)) do registro da MemoryTable no momento de cada interação do Loop. E recebemos o valore referente ao campo da tabela em questão.
Após terminar o loop, nós precisamos apenas executar a query usando o método Execute do qryAuxiliar1. O primeiro parâmetro serve para indicarmos quantas vezes será executado o comando, nesse caso o número de registros presentes no MemoryTable. O segundo parâmetro deixamos em 0 (zero) pois indica ao query que iremos começar as inserções a partir do primeiro registro.
Por fim apenas fechamos a tabela. Esse mecanismo pode ser usado para basicamente qualquer inserção em lote que você precise efetuar em seus sistemas.
A última providência que devemos tomar é retornar ao evento do botão Sincronizar e adiciona a linha abaixo para fazer a chamada ao método AtualizarTitulos.
//Chamamos a rotina para incluir os dados recebidos na tabela Temporária, //dentro da tabela no SQLite. AtualizarTitulos(memTemporario);
Otimizando Memória
Vamos voltar no tempo até a primeira parte de nosso tutorial. Nós criamos um método no frmMain chamado ShowForm que recebe um TFmxObject como parâmetro. Esse método é responsável por abrir os formulários de nosso aplicativo móvel. Recordemos o código:
procedure TfrmMain.ShowForm(AObject: TFmxObject); var I : Integer; begin for I := 0 to Pred(Self.lytMain.Children.Count) do TControl(Self.lytMain.Children[I]).Visible := TControl(Self.lytMain.Children[I]) = TControl(AObject); end;
Listagem 4. Código do ShowForm
A codificação aqui é bem simples como podemos ver. Apenas estamos deixando todos os formulários invisíveis, exceto o formulário recebido no parâmetro. Entretanto temos um problema aqui, memória. Por enquanto temos apenas um formulário a mais, frmTitulos. Quando tivermos mais formulários poderemos ter problemas de estouro de memória, algo que em Desktop pode ser facilmente ignorado pois temos mais processamento e mais memória, embora não seja uma boa prática manter MemoryLeaks.
if not Assigned(FTitulos) then FTitulos := TfrmTitulos.Create(Self);
Pegando como exemplo o item de menu Titulos, vemos que estamos fazendo um primeiro teste para verificar se existe instância do formulário de títulos e o criamos usando um Create(Self). A liberação de memória desse formulário, definitiva, fazemos no OnClose do formulário principal.
if Assigned(FTitulos) then FTitulos.DisposeOf;
Até aqui nenhum problema e nenhuma novidade. Iniciantes e usuários Avançados do Delphi irão concordar que não há nada de novo. Mas precisamos pensar na hipótese iminente de termos mais formulários. Quando isso acontecer, não podemos deixar nossos forms sempre ativos na memória, pois como mencionado anteriormente, poderemos receber mensagens de erros e um grande problema com MemoryLeaks. Resolvi esse problema de forma até simples, embora trabalhosa.
#Dica: MemoryLeaks, para os leigos, é o estouro de memória na aplicação, ou seja, objetos não liberados de memória.
Vamos lá. No mesmo evento ShowForm criaremos uma lista de objetos a serem destruídos e então os liberaremos de memória. Para começarmos essa rotina, primeiro precisamos de um novo formulário. Pegando a ideia inicial de nosso app, vamos criar um novo formulário chamado frmUsuarios. Clique em File > New > Multi-Device Form – Delphi. Use HD Form como template. Modifique a propriedade Name para frmUsuarios e salve a unit como UntUsuarios. Não faremos nada com o formulário agora. Apenas o usaremos como parte de nossa rotina para testes em nossa versão 2 de ShowForm.
Lembre-se de retirar a variável com o nome do formulário na área Interface do form. Localize a instrução no código-fonte e apague-a.
var frmUsuarios: TfrmUsuarios;
Retorne ao formulário principal e adicione o novo form a seção Interface usando File > Use Unit. Crie uma nova variável em Private com o nome FUsuarios do tipo TfrmUsuarios. No evento OnClick do segundo item de menu, digite o código da Listagem 5:
procedure TfrmMain.lstitUsuariosClick(Sender: TObject); begin if not Assigned(FUsuarios) then FUsuarios := TfrmUsuarios.Create(Self); MultiView1.MasterButton := FUsuarios.spbBack; {Forçamos o fechamento do menu no clique do item} MultiView1.HideMaster; {Adiciona o Layout de Pedidos ao Layout Principal que controla tudo} lytMain.AddObject(FUsuarios.lytUsuarios); {Deixa todos os layouts invisíveis e deixa o layout alvo visível} ShowForm(FUsuarios.lytUsuarios); end;
Listagem 5. Código de abertura do formulário de usuários
Isso já será suficiente para termos acesso aos usuários. Claro, precisamos trabalhar no form, mas isso deixarei a cargo de cada um. O que quero que observem aqui é a primeira linha do código. Estamos testando a variável FUsuarios e criando o formulário se ele não existir, como fizemos com o form de títulos. Se executarmos o app, poderemos alternar agora de usuários para títulos e vice-versa, mas o formulário de usuários sempre estará no ar, exceto quando o usuário fechar a aplicação. O que faremos?
Acesse o método ShowForm e vamos começar a trabalhar nele. Digite o código completo da Listagem 6.
procedure TfrmMain.ShowForm(AObject: TFmxObject); var I : Integer; strFRM : String; FListaForms : TStringList; begin FListaForms := TStringList.Create; for I := 0 to Pred(Self.lytMain.Children.Count) do begin TControl(Self.lytMain.Children[I]).Visible := TControl(Self.lytMain.Children[I]) = TControl(AObject); strFRM := UpperCase(TControl(Self.lytMain.Children[I]).Owner.Name); if ((strFRM <> UpperCase(frmMain.Name)) and (strFRM <> UpperCase(FTitulos.Name))) and not (TControl(Self.lytMain.Children[I]).Visible) then {Adiciona na Lista de Destruição dos Forms} FListaForms.Add(strFRM); end; for I := Pred(FListaForms.Count) downto 0 do begin if (Assigned(FUsuarios)) and (FListaForms[I] = UpperCase(FUsuarios.Name)) then begin FUsuarios.DisposeOf; FUsuarios := nil; FListaForms.Delete(I); end; end; FListaForms.DisposeOf; end;
Listagem 6. Código modificado de ShowForm
Repare que fizemos modificações significativas. Criamos duas variáveis para receber o nome do formulário e outra para gerenciar os objetos que serão destruídos. Basicamente o que faremos é detectar quando trocamos de um formulário para outro e destruímos o formulário anterior, exemplo:
- Quando o usuário acessar o menu Usuários / Clientes, criaremos o formulário na memória.
- Ao retornar para Títulos, destruiremos Usuários / Clientes.
- Assim acontecerá com outros objetos.
Nesse esquema, o único formulário que não será destruído, por ser nosso default, será o de Títulos. Por isso detectamos quando estamos passando por sua instância e não o destruímos.
Criamos a variável FListForms do tipo TStringList e iniciamos o Loop que já tínhamos antes, porém com uma alteração. A variável strFRM recebe o nome do formulário da interação atual do Loop. Se esse formulário NÃO for o formulário Principal nem Títulos, adicionamos ele a lista de formulários a serem destruídos ou não.
Após o término do primeiro loop, fazemos um loop na lista de formulários. Ai começa nossa mágica que não é nada complicada. Nós fazemos um loop de trás para frente na lista de formulários (FListaForms). Se o formulário estiver criado em memória E for igual a iteração atual do loop, então liberamos ele usando DisposeOf, marcamos sua variável como NIL e deletamos na lista de formulários.
Eu já expliquei, mas não custa falar novamente. Usamos em Mobile o método DisposeOf, recomendação encontrada em eBooks do Marco Cantù e outros autores. O .Free ou .FreeAndNil, como estamos acostumados, funciona, entretanto pode demorar até que o sistema operacional passe pelo objeto e o libere de memória. Quem faz isso é o GarbageCollection (Coletor de Lixo) no Android e iOS. Para evitar que o formulário demore para ser destruído, usamos DisposeOf que envia instrução ao sistema operacional para destruir imediatamente o nosso objeto. Isso funciona não só para formulários, mas para outras variáveis e objetos. É o que fazemos logo em seguida.
FListaForms.DisposeOf;
Com todas essas verificações e cuidados, conseguimos manter um app mais fluido e confiável.
Conclusões
Fizemos basicamente duas grandes modificações nesse tutorial. Aprendemos a aproveitar o conteúdo recebido do servidor DataSnap e incluímos os registros em nossa base de dados local usando ArrayDML. Também vimos como fazer uma modificação significativa em nossa rotina de abertura de formulários para evitar problemas de MemoryLeak no futuro.

Author: Adriano Santos
1 comentário
Comments are closed.
[…] Tutorial: Criando meu app step-by-step – ArrayDML – Parte VIII […]