Bayesean Blog - Desktop, Mobile and IOT Developer Blog


Delphi FMX REST Client App Part 4

Posted on 30th Nov 2017 in Delphi > XE4


restserver.png

Background

This is the Firemonkey version of Part 3 VCL version making use of the newer RESTclient, RESTrequest and RESTresponse library. This forms part of a move from VCL to FMX. In this case using the REST components required a full rewrite.

Doing this also would give me the writer an opportunity to gain a Developers emotional feel for the effective comparison of the Indy library and the Newer REST library and are expressed in the Summary.

Scope

Continue with the same theme that was used in Part 1 to 3.

Upgrade the project to newer REST - Components available with the latest current 10.2.1 Tokyo IDE - Enterprise.

Use the FMX platform rather than VCL. The same can be created in VCL. However all my projects are being converted or rewritten in FMX to take advantage of the cross-platform option.

For this Project, keep the application and functionality feel to be the same as the VCL project where possible.

Summarise the positives and negatives using Indy vs Rest products as a developers’ tool.

Project Component Changes

For this FMX project, the following were replaced:-

INDY Components  -> RESTClient Components.

Third Party SuperObject (json library)  -> No json code written  (However wrote a simple Json parser for the Database.

Clientdataset  -> FDmemtable.

Project Layout

FMXclientr.png32.png

Layout and Rest Structure

A feature that is offered by Embarcadero is the REST Debugger which allows you to connect directly to your Rest Server.

Once connected and you have viewed the tabular data use the ‘Copy Components’  Button which allows all the required components to be copied directly into your application.

Whilst this allows you to set up a working application with no code written and a great Embarcadero team’s quick implementation in reality there will be many different calls that will be required to be made to a typical REST Server.  Using the REST Debugger, a new full Rest component set is created for each individual Get, Post, Delete and Header URI call made resulting in a Datamodule full of REST components.

I see no harm in having this except that it would add great overhead to your application. Creating a Windows Application is of no consequence these days however when writing for mobile you are penalized by the size of your created app and forced to focus on reduced overhead inclusion. Actually you need provide overhead to allow threaded code to pull your app together.

The only time that you may have multiple components, is if you were making multiple calls at the same time asynchronously.  

For this Application only one call is made at a time so it requires One RESTclient ,RESTrequest and RESTresponse  component ,used with Two FDmemtables , each connected to these REST-components with a RESTDataSetAdapter. So in the datamodule we have one set in place.

A FDStanStorageJsonLink  allows json data file to be saved and loaded.

This is used to simulate a Database that would normally be connected to your application.

Each FDmemtable is connected to a Tgrid using a Bindsource.

A tabcontrol is set up with two tabs.

Tabitem1 – Local Pricelist

This autoloads the simulated pricelist from the Database.

7 options are available:-

Load the Webserver Pricelist.

Load from Database

Save to Database

Create a new Item in Database

Edit a Database Item

Delete an item in the database

Update the Webserver Pricing with Current changed or loaded Database.

 

Tabitem2  – Webserver Pricelist View.

This auto loads the Webserver Pricelist from the Webserver.

Three options are available :-

  • Get One item
  • Get All items
  • Save to the Simulated Database.

 

FMXclientr.png32.png

 fmxedit.png14.png

 

 

Here we make use of the RESTResponseDatasetAdapter that will parse the Json data into a memTable.

This is part of the built in functionalty of the Rest Components.  Actually a nice feature!

The code for calling a Get All data from the Webserver as follows:-

 

procedure Tfmmain.getwebbtnClick(Sender: TObject);
var
 JValue:TJSONValue;
begin
 Memo2.clearcontent;
 dm.RESTResponseDataSetAdapter1.Dataset:= DM.FDMemTable2;
 dm.RESTResponseDataSetAdapter1.Active:= true;
 dm.RESTClient1.BaseURL:= 'https://www.bayeseanblog.com/blog/public/demo/salesrest/public/api/pricing';
 dm.RESTRequest1.method:= TRESTRequestMethod.rmGET;
 try
 dm.RESTRequest1.Execute;
 if dm.restresponse1.StatusCode <> 200 then
 begin
 memo1.Text:= dm.restresponse1.Errormessage;
 exit
 end;
   jValue:=dm.RESTResponse1.JSONValue;
 Memo2.Text:= jValue.ToString;
 except
  on exception:ERESTException do
  Showmessage('Could not connect to Webserver');
  on exception:ERequestError do
   Showmessage('Could not connect to the Internet');
      end;
end;

procedure Tfmmain.btngetallClick(Sender: TObject);
var
  JValue:TJSONValue;
begin
 dm.RESTClient2.Bindsource.Active:=false;
 Memo1.Lines.Clear;
 dm.RESTClient2.Bindsource.Active:=true;

 dm.RESTResponseDataSetAdapter1.dataset:= dm.FDMemTable1;
 dm.RESTResponseDataSetAdapter1.Active:=true;
 dm.RESTClient1.BaseURL:= 'https://www.bayeseanblog.com/blog/public/demo/salesrest/public/api/pricing';
 dm.RESTRequest1.method:= TRESTRequestMethod.rmGET;
 dm.RESTRequest1.Execute;
 //get response message status
 if dm.restresponse1.StatusCode <> 200 then
begin
 memo1.lines.Add(dm.restresponse1.Errormessage);
  end
 else
 begin
 jValue:=dm.RESTResponse1.JSONValue;
 Memo1.lines.Add(jValue.ToString);
 end;
 dm.RESTResponseDataSetAdapter1.dataset:= dm.FDMemTable2;
 dm.RESTResponseDataSetAdapter1.Active:=true;
  end;
{/code}

This is the routine to Post to the REST WebServer. This first calls a delete all on the REST WebServer and then posts the new data directly to the WebServer. This is the easiest way to ensure data integrity for this process.
{code}
procedure Tfmmain.PosttowebbtnClick(Sender: TObject);
var
  JValue:TJSONValue;
  JText:string;
  jparse:string;
begin
  Memo1.Lines.Clear;
   //delete all items on the web
 dm.RestRequest2.Body.ClearBody;
 dm.RESTClient2.BaseURL:= 'https://www.bayeseanblog.com/blog/public/demo/salesrest/public/api/pricing/delete/0';
 dm.RESTRequest2.method:= TRESTRequestMethod.rmDelete;
 dm.RESTRequest2.Execute;

 memo1.lines.add('Deleting all Data on Webserver.....');
  memo1.lines.add(dm.RESTResponse2.Content + '   Status Code : ' +inttostr(dm.RESTResponse2.StatusCode) + ' '+ dm.RESTResponse2.StatusText  );

 memo1.lines.add('');
 memo1.lines.add('Adding Data to Webserver.....');

  dm.RestRequest2.Body.ClearBody;
    //dm.RESTRequest2.Body.Add(Jtext,Trestcontenttype.ctTEXT_PLAIN);
  dm.RESTRequest2.Body.Add(setJsondatafromtable(dm.fdmemtable1),Trestcontenttype.ctAPPLICATION_JSON);
  dm.RESTClient2.BaseURL:= 'https://www.bayeseanblog.com/blog/public/demo/salesrest/public/api/pricing/add/all';
  dm.RESTRequest2.method:= TRESTRequestMethod.rmPost;
  dm.RESTRequest2.Execute;

  Memo1.lines.Add(dm.RESTResponse2.content+ '   Status Code : ' +inttostr(dm.RESTResponse2.StatusCode) + ' '+ dm.RESTResponse2.StatusText );
  memo1.lines.Add(setJsondatafromtable(dm.fdmemtable1));
  dm.FDMemTable1.First;
   end;
{/code}

In FormShow the Fielddefs are created for the two memtables. This is required as a simulated Database is used for this example.

procedure Tfmmain.FormShow(Sender: TObject);
begin
 with dm do
  begin
  FDMemTable1.FieldDefs.Add('id',ftWideString,20,False);
  FDMemTable1.FieldDefs.Add('sku',ftWidestring,20,False);
  FDMemTable1.FieldDefs.Add('skusize',ftWidestring,20,False);
  FDMemTable1.FieldDefs.Add('barcode',ftWidestring, 20,false);
  FDMemTable1.FieldDefs.Add('description',ftWidestring,100,False);
  FDMemTable1.FieldDefs.Add('price',ftWidestring,30,false);
  FDmemtable1.CreateDataset;

  FDMemTable2.FieldDefs.Add('id',ftWideString,20,False);
  FDMemTable2.FieldDefs.Add('sku',ftWidestring,20,False);
  FDMemTable2.FieldDefs.Add('skusize',ftWidestring,20,False);
  FDMemTable2.FieldDefs.Add('barcode',ftWidestring, 20,false);
  FDMemTable2.FieldDefs.Add('description',ftWidestring,100,False);
  FDMemTable2.FieldDefs.Add('price',ftWidestring,30,false);
  FDmemtable2.CreateDataset;
end;

Edit2.Text:= '1';
//set the tab to loaded file
loadfromsimulatedDB;
tabcontrol1.ActiveTab:= tabitem2;

//populate from the Web
getwebbtnclick(self);
end;
function Tfmmain.setJsondatafromtable(memtable: TFDmemtable):string;
var
openbracket:string;
endbracket:string;
begin
memtable.DisableControls;
memtable.active:= true;
memtable.first;
openbracket:= '[';
endbracket:= ']';
while not memtable.Eof do
begin
if memtable.RecNo = 1 then
begin

result:= openbracket+
ParsetoJson(
 // dm.FDMemTable1.FieldByName('id').Asstring,
  dm.FDMemTable1.FieldByName('sku').Asstring,
  dm.FDMemTable1.FieldByName('skusize').Asstring,
   dm.FDMemTable1.FieldByName('description').Asstring,
  dm.FDMemTable1.FieldByName('barcode').Asstring,
  dm.FDMemTable1.FieldByName('price').Asstring)+',';
end;
if(( memtable.RecNo >1 )and (memtable.RecNo < memtable.RecordCount) )then
begin
Result:= result + ParsetoJson(
//  dm.FDMemTable1.FieldByName('id').Asstring,
  dm.FDMemTable1.FieldByName('sku').Asstring,
  dm.FDMemTable1.FieldByName('skusize').Asstring,
   dm.FDMemTable1.FieldByName('description').Asstring,
  dm.FDMemTable1.FieldByName('barcode').Asstring,
  dm.FDMemTable1.FieldByName('price').Asstring) +',';
end;

 if(memtable.RecNo = memtable.RecordCount) then
begin
result:= result+ ParsetoJson(
 // dm.FDMemTable1.FieldByName('id').Asstring,
  dm.FDMemTable1.FieldByName('sku').Asstring,
  dm.FDMemTable1.FieldByName('skusize').Asstring,
   dm.FDMemTable1.FieldByName('description').Asstring,
  dm.FDMemTable1.FieldByName('barcode').Asstring,
  dm.FDMemTable1.FieldByName('price').Asstring) +endbracket;
end;

 memtable.next;
end;
memtable.First;
memtable.EnableControls
end;
end.
procedure Tfmmain.getwebbtnClick(Sender: TObject);
var
 JValue:TJSONValue;
begin
 Memo2.clearcontent;
 dm.RESTResponseDataSetAdapter1.Dataset:= DM.FDMemTable2;
 dm.RESTResponseDataSetAdapter1.Active:= true;
 dm.RESTClient1.BaseURL:= 'https://www.bayeseanblog.com/blog/public/demo/salesrest/public/api/pricing';
 dm.RESTRequest1.method:= TRESTRequestMethod.rmGET;
 try
 dm.RESTRequest1.Execute;
 if dm.restresponse1.StatusCode <> 200 then
 begin
 memo1.Text:= dm.restresponse1.Errormessage;
 exit
 end;
   jValue:=dm.RESTResponse1.JSONValue;
 Memo2.Text:= jValue.ToString;
 except
  on exception:ERESTException do
  Showmessage('Could not connect to Webserver');
  on exception:ERequestError do
   Showmessage('Could not connect to the Internet');
      end;
end;

procedure Tfmmain.btngetallClick(Sender: TObject);
var
  JValue:TJSONValue;
begin
 dm.RESTClient2.Bindsource.Active:=false;
 Memo1.Lines.Clear;
 dm.RESTClient2.Bindsource.Active:=true;

 dm.RESTResponseDataSetAdapter1.dataset:= dm.FDMemTable1;
 dm.RESTResponseDataSetAdapter1.Active:=true;
 dm.RESTClient1.BaseURL:= 'https://www.bayeseanblog.com/blog/public/demo/salesrest/public/api/pricing';
 dm.RESTRequest1.method:= TRESTRequestMethod.rmGET;
 dm.RESTRequest1.Execute;
 //get response message status
 if dm.restresponse1.StatusCode <> 200 then
begin
 memo1.lines.Add(dm.restresponse1.Errormessage);
  end
 else
 begin
 jValue:=dm.RESTResponse1.JSONValue;
 Memo1.lines.Add(jValue.ToString);
 end;
 dm.RESTResponseDataSetAdapter1.dataset:= dm.FDMemTable2;
 dm.RESTResponseDataSetAdapter1.Active:=true;
  end;
{/code}

This is the routine to Post to the REST WebServer. This first calls a delete all on the REST WebServer and then posts the new data directly to the WebServer. This is the easiest way to ensure data integrity for this process.
{code}
procedure Tfmmain.PosttowebbtnClick(Sender: TObject);
var
  JValue:TJSONValue;
  JText:string;
  jparse:string;
begin
  Memo1.Lines.Clear;
   //delete all items on the web
 dm.RestRequest2.Body.ClearBody;
 dm.RESTClient2.BaseURL:= 'https://www.bayeseanblog.com/blog/public/demo/salesrest/public/api/pricing/delete/0';
 dm.RESTRequest2.method:= TRESTRequestMethod.rmDelete;
 dm.RESTRequest2.Execute;

 memo1.lines.add('Deleting all Data on Webserver.....');
  memo1.lines.add(dm.RESTResponse2.Content + '   Status Code : ' +inttostr(dm.RESTResponse2.StatusCode) + ' '+ dm.RESTResponse2.StatusText  );

 memo1.lines.add('');
 memo1.lines.add('Adding Data to Webserver.....');

  dm.RestRequest2.Body.ClearBody;
    //dm.RESTRequest2.Body.Add(Jtext,Trestcontenttype.ctTEXT_PLAIN);
  dm.RESTRequest2.Body.Add(setJsondatafromtable(dm.fdmemtable1),Trestcontenttype.ctAPPLICATION_JSON);
  dm.RESTClient2.BaseURL:= 'https://www.bayeseanblog.com/blog/public/demo/salesrest/public/api/pricing/add/all';
  dm.RESTRequest2.method:= TRESTRequestMethod.rmPost;
  dm.RESTRequest2.Execute;

  Memo1.lines.Add(dm.RESTResponse2.content+ '   Status Code : ' +inttostr(dm.RESTResponse2.StatusCode) + ' '+ dm.RESTResponse2.StatusText );
  memo1.lines.Add(setJsondatafromtable(dm.fdmemtable1));
  dm.FDMemTable1.First;
   end;
{/code}

In FormShow the Fielddefs are created for the two memtables. This is required as a simulated Database is used for this example.

procedure Tfmmain.FormShow(Sender: TObject);
begin
 with dm do
  begin
  FDMemTable1.FieldDefs.Add('id',ftWideString,20,False);
  FDMemTable1.FieldDefs.Add('sku',ftWidestring,20,False);
  FDMemTable1.FieldDefs.Add('skusize',ftWidestring,20,False);
  FDMemTable1.FieldDefs.Add('barcode',ftWidestring, 20,false);
  FDMemTable1.FieldDefs.Add('description',ftWidestring,100,False);
  FDMemTable1.FieldDefs.Add('price',ftWidestring,30,false);
  FDmemtable1.CreateDataset;

  FDMemTable2.FieldDefs.Add('id',ftWideString,20,False);
  FDMemTable2.FieldDefs.Add('sku',ftWidestring,20,False);
  FDMemTable2.FieldDefs.Add('skusize',ftWidestring,20,False);
  FDMemTable2.FieldDefs.Add('barcode',ftWidestring, 20,false);
  FDMemTable2.FieldDefs.Add('description',ftWidestring,100,False);
  FDMemTable2.FieldDefs.Add('price',ftWidestring,30,false);
  FDmemtable2.CreateDataset;
end;

Edit2.Text:= '1';
//set the tab to loaded file
loadfromsimulatedDB;
tabcontrol1.ActiveTab:= tabitem2;

//populate from the Web
getwebbtnclick(self);
end;
function Tfmmain.setJsondatafromtable(memtable: TFDmemtable):string;
var
openbracket:string;
endbracket:string;
begin
memtable.DisableControls;
memtable.active:= true;
memtable.first;
openbracket:= '[';
endbracket:= ']';
while not memtable.Eof do
begin
if memtable.RecNo = 1 then
begin

result:= openbracket+
ParsetoJson(
 // dm.FDMemTable1.FieldByName('id').Asstring,
  dm.FDMemTable1.FieldByName('sku').Asstring,
  dm.FDMemTable1.FieldByName('skusize').Asstring,
   dm.FDMemTable1.FieldByName('description').Asstring,
  dm.FDMemTable1.FieldByName('barcode').Asstring,
  dm.FDMemTable1.FieldByName('price').Asstring)+',';
end;
if(( memtable.RecNo >1 )and (memtable.RecNo < memtable.RecordCount) )then
begin
Result:= result + ParsetoJson(
//  dm.FDMemTable1.FieldByName('id').Asstring,
  dm.FDMemTable1.FieldByName('sku').Asstring,
  dm.FDMemTable1.FieldByName('skusize').Asstring,
   dm.FDMemTable1.FieldByName('description').Asstring,
  dm.FDMemTable1.FieldByName('barcode').Asstring,
  dm.FDMemTable1.FieldByName('price').Asstring) +',';
end;

 if(memtable.RecNo = memtable.RecordCount) then
begin
result:= result+ ParsetoJson(
 // dm.FDMemTable1.FieldByName('id').Asstring,
  dm.FDMemTable1.FieldByName('sku').Asstring,
  dm.FDMemTable1.FieldByName('skusize').Asstring,
   dm.FDMemTable1.FieldByName('description').Asstring,
  dm.FDMemTable1.FieldByName('barcode').Asstring,
  dm.FDMemTable1.FieldByName('price').Asstring) +endbracket;
end;

 memtable.next;
end;
memtable.First;
memtable.EnableControls
end;
end.
{/code}
The Jason parser to parse from the Database into Json Format .
{code}
unit jsonparser;


interface
uses System.Json;

function ParsetoJson(sku,skusize,barcode,description,price:string):string ;

implementation

 function ParsetoJson(sku,skusize,barcode,description,price:string):string ;
 var
  Obj: TJSONObject;
 begin
    Obj := TJSONObject.Create;
  //  Obj.AddPair('id', index);
    Obj.AddPair('sku', sku);
    Obj.AddPair('skusize', skusize);
    Obj.AddPair('barcode', barcode);
    Obj.AddPair('description', description);
    Obj.AddPair('price', price);
    result:=  Obj.toJson ;
 obj.Free;
 end;

 

Summary

The FMX Application was quicker to write with less code than with the Indy Library using the ‘SuperObjects’ Json library.

However when integrating the REST side, I found writing the Vcl application Part 3 easier using Indy components as the required call logic was typical ‘Delphi’ in nature. So figuring out how to call custom headers was logical.

Using One set of Rest components, calling custom headers using the ‘Rest-Request’ component would not work out of the box especially when making multiple different calls processing different Custom Headers. Once I found the solution, then all worked and all was simple thereafter.

Firemonkey logic feels like learning a new language all over again but one gets used to its way. Fortunately FMX makes for writing less and less functions, procedures and properties. However typically the first 80% of the app is completed quickly…it is the last 20% that becomes time consuming as not everything always ties naturally together and there is ALWAYS a lot of head scratching to eventually make the App function as it should.

That being said, the rewards on completion cannot be understood unless you are a Delphi developer. 

The Rest components have OpenSSL directly built -in and this makes ‘https’ calling straight forward.  Google and others are on a charge to encourage all websites to use ‘https.’ It is best to consider that in a few years or less, ‘http’ will not be used in the main stream in the furture.

For Indy you will need an OpenSSL.dll or OpenSSL.so included in your deployment .

Be aware that using OpenSSL binary is not straight forward when writing an Android Application as it will clash with Android’s change to ‘Boring SSL’ in Android 6 and up. Fortunately there are alternative Binaries available on the Embarcadero Forum that will eliminate the clash. Just means a little bit more work to implement Indy components.

Perhaps Embarcadero should incorporate OpenSSL internally into the Indy components that have been evolving over time.

The Rest components have a lot to offer and with Oauth2 built in capability makes the implementation far quicker than using the Indy components. Be prepared for more secure rest security connection methods. Oauth2 will soon be out done by a new more secure protocol.  Oauth3?  Perhaps something New??

Both Indy and Rest components work fast enough for my requirement and I did not measure the speed efficiency between them. It is more important to ensure that on passing a https call, that your Rest Service is providing the necessary speed required.

Fortunately a combination of Indy and the Rest components will ensure that just about all is covered.

Read the final Part 5 covering Authentication for both PHP Rest Server and Delphi VCL and Firemonkey Client Apps at the blogsite.

Feel free to download the Application Source Code off Github.

Download Code

 Happy Coding 


Be the First to Comment...

Add a Comment

9+5

Recent News

Delphi Delimited String to Fields
Delphi A Professional VCL DBGrid Part Four
Delphi A Professional VCL DBGrid Part Three
Delphi A Professional VCL DBGrid Part Two
Delphi A Professional VCL DBGrid Part One
Delphi VCL Buttons in DBGrid
Two Helper Apps for Delphi LibUSB
Delphi Libusb Library Introduction

Categories

Bootstrap 4
Delphi VCL
Delphi FMX
Ajax
Bootstrap 3
CSS
XE4>Delphi > XE4
Delphi < XE4
PHP

Archives

August 2019

Delphi Delimited String to Fields

June 2019

Delphi A Professional VCL DBGrid Part Four

May 2019

Delphi A Professional VCL DBGrid Part Three

April 2019

Delphi A Professional VCL DBGrid Part Two

March 2019

Delphi A Professional VCL DBGrid Part One

November 2018

Delphi VCL Buttons in DBGrid

October 2018

Two Helper Apps for Delphi LibUSB

September 2018

Delphi Libusb Library Introduction

August 2018

Delphi Object directly to a Json string in a REST Client
Delphi using Environment Variables in your App

July 2018

Delphi FMX Leaflet Plotter using OSM Maps

June 2018

C2PAS32 Convertor Application
C to Delphi Open Source Convertors Shootout
Delphi command-line programs with DOSCommand

May 2018

Delphi PDF Embedded viewer with PDF.js

March 2018

Delphi FMX - Changing TCharacter to TCharHelper
Make Your Delphi App POP using Javascript!

January 2018

Delphi FMX Dashboard using Chart.JS
Delphi FMX Form Docking

December 2017

PHP Slim REST Server & Delphi Auth Part 5

November 2017

Delphi FMX REST Client App Part 4

October 2017

Delphi VCL REST Pricing Client App Part 3

September 2017

Delphi REST VCL Client Basic Auth Part 2B

August 2017

Delphi REST Client Part 2A
PHP PDO REST Server Part 1

July 2017

PHP REST Server and Delphi Client Intro

June 2017

Delphi SQLite Encryptor-Decryptor Tool
Updating Applications Manifest using Delphi

May 2017

Create a Visual IP Address Geolocation with PHP

March 2017

PHP Downloader using Countdown timer
PHP File Downloader from a Inbox Selection

February 2017

Javascript Image-File Uploader with ThumbViewer

January 2017

Morris Charts and PHP-PDO

December 2016

CSS to create a functional Toggle Button