Tina4

Chapter 13: Design Patterns & Best Practices#

What We Learned the Hard Way#

You call REST.Get and forget to free the response. The app leaks memory. You parse JSON in the main thread and the UI freezes for two seconds. You hardcode the API key in the source and push it to GitHub. You rebuild the entire HTML page when only one label needs updating.

Every pattern in this chapter was discovered through real-world mistakes. Every best practice exists because someone did it the wrong way first. This is the chapter you read before you ship -- and the chapter you return to when something breaks and you cannot figure out why.


1. Memory Management#

Delphi does not have a garbage collector. Every object you create must be freed. The Tina4 components create TJSONObject instances that you own. If you do not free them, the memory leaks silently until the application crashes.

The try/finally Pattern#

Every TJSONObject returned by TTina4REST must be freed:

pascal
// WRONG -- leaks memory if an exception occurs between Get and Freevar Response := REST.Get(StatusCode, '/users');ShowMessage(Response.ToString);Response.Free;โ€‹// RIGHT -- guaranteed cleanupvar Response := REST.Get(StatusCode, '/users');try  ShowMessage(Response.ToString);finally  Response.Free;end;

This applies to every REST method: Get, Post, Patch, Put, Delete. They all return a TJSONObject that you own.

Parsing JSON Safely#

StrToJSONObject returns nil on failure. Always check:

pascal
// WRONG -- access violation if JSON is malformedvar Obj := StrToJSONObject(SomeString);ShowMessage(Obj.GetValue<string>('name'));Obj.Free;โ€‹// RIGHT -- nil check before usevar Obj := StrToJSONObject(SomeString);try  if Assigned(Obj) then    ShowMessage(Obj.GetValue<string>('name'))  else    ShowMessage('Invalid JSON');finally  Obj.Free;  // Free is safe to call on nilend;

Component Ownership#

Components dropped on a form at design time are owned by the form. The form frees them automatically. Components created at runtime need explicit ownership:

pascal
// Owned by Self (the form) -- freed automaticallyvar Adapter := TTina4JSONAdapter.Create(Self);Adapter.MemTable := FDMemTable1;โ€‹// No owner -- you must free it yourselfvar Twig := TTina4Twig.Create(nil);try  Twig.Render('template.html', Variables);finally  Twig.Free;end;

Rule of thumb: if the component lives for the form's lifetime, pass Self as owner. If it is temporary, pass nil and use try/finally.

MemTable Lifecycle#

TFDMemTable does not connect to a database, so it does not need explicit cleanup of connections. But be aware of when data is valid:

pascal
// WRONG -- MemTable might not be activeShowMessage(MemUsers.FieldByName('name').AsString);โ€‹// RIGHT -- check Active and RecordCountif MemUsers.Active and (MemUsers.RecordCount > 0) then  ShowMessage(MemUsers.FieldByName('name').AsString)else  ShowMessage('No data loaded');

2. Async Patterns#

The Problem with Synchronous Calls#

ExecuteRESTCall blocks the main thread. The UI freezes until the response arrives. On a slow network, the user sees a frozen window and thinks the app crashed.

pascal
// WRONG -- blocks UI threadRESTUsers.ExecuteRESTCall;  // 2-second freezeUpdateUI;

The Async Solution#

pascal
// RIGHT -- non-blockingRESTUsers.OnExecuteDone := procedure(Sender: TObject)begin  TThread.Synchronize(nil, procedure  begin    UpdateUI;  end);end;RESTUsers.ExecuteRESTCallAsync;

ExecuteRESTCallAsync runs the HTTP call on a background thread. When it completes, OnExecuteDone fires. But OnExecuteDone fires on the background thread, not the main thread. You must use TThread.Synchronize to update UI controls.

Loading Indicators#

Show a loading state while the async call runs:

pascal
procedure TFormMain.FetchUsersAsync;begin  { Show loading }  Renderer.SetElementText('userTableBody', 'Loading...');  Renderer.SetElementVisible('loadingSpinner', True);โ€‹  RESTUsers.OnExecuteDone := procedure(Sender: TObject)  begin    TThread.Synchronize(nil, procedure    begin      { Hide loading, show data }      Renderer.SetElementVisible('loadingSpinner', False);      RefreshUserTable;    end);  end;  RESTUsers.ExecuteRESTCallAsync;end;

Multiple Concurrent Requests#

When you need data from multiple endpoints simultaneously:

pascal
procedure TFormMain.LoadDashboard;var  StatsLoaded, UsersLoaded: Boolean;begin  StatsLoaded := False;  UsersLoaded := False;โ€‹  Renderer.HTML.Text :=    '<div style="padding: 20px;">' +    '  <p id="loadStatus">Loading dashboard data...</p>' +    '</div>';โ€‹  RESTStats.OnExecuteDone := procedure(Sender: TObject)  begin    TThread.Synchronize(nil, procedure    begin      StatsLoaded := True;      if StatsLoaded and UsersLoaded then        RenderFullDashboard;    end);  end;โ€‹  RESTUsers.OnExecuteDone := procedure(Sender: TObject)  begin    TThread.Synchronize(nil, procedure    begin      UsersLoaded := True;      if StatsLoaded and UsersLoaded then        RenderFullDashboard;    end);  end;โ€‹  RESTStats.ExecuteRESTCallAsync;  RESTUsers.ExecuteRESTCallAsync;end;

Both requests fire simultaneously. The dashboard renders when both complete. This is faster than sequential calls.

Cancellation and Timeouts#

The underlying HTTP client supports timeouts. Set them on the TTina4REST component:

pascal
REST.Timeout := 10000;  // 10 secondsโ€‹RESTUsers.OnExecuteDone := procedure(Sender: TObject)begin  TThread.Synchronize(nil, procedure  begin    if RESTUsers.LastStatusCode = 0 then      ShowNotification('Request timed out.', True)    else      RefreshUserTable;  end);end;RESTUsers.ExecuteRESTCallAsync;

3. Error Handling Patterns#

Status Code Checking#

Every REST response has a status code. Check it before using the data:

pascal
procedure TFormMain.HandleResponse(StatusCode: Integer; Response: TJSONObject);begin  case StatusCode of    200:      ProcessData(Response);    201:      ShowNotification('Created successfully.');    204:      ShowNotification('Deleted successfully.');    400:      ShowNotification('Bad request: check your input.', True);    401:      begin        ShowNotification('Session expired. Please log in again.', True);        DoLogout;      end;    403:      ShowNotification('Access denied.', True);    404:      ShowNotification('Resource not found.', True);    422:      begin        { Extract validation errors from response }        if Assigned(Response) then        begin          var Errors := Response.GetValue<TJSONArray>('errors');          if Assigned(Errors) then          begin            var Msg := '';            for var I := 0 to Errors.Count - 1 do              Msg := Msg + Errors.Items[I].GetValue<string>('message') + #13#10;            ShowNotification(Msg.Trim, True);          end;        end        else          ShowNotification('Validation failed.', True);      end;    500..599:      ShowNotification('Server error. Please try again later.', True);  else    ShowNotification('Unexpected response: ' + StatusCode.ToString, True);  end;end;

JSON Parse Failure#

Never assume JSON parsing will succeed:

pascal
// WRONG -- crashes on malformed JSONvar Users := Response.GetValue<TJSONArray>('records');for var I := 0 to Users.Count - 1 do  ProcessUser(Users.Items[I] as TJSONObject);โ€‹// RIGHT -- defensive parsingvar UsersValue := Response.FindValue('records');if (UsersValue <> nil) and (UsersValue is TJSONArray) thenbegin  var Users := UsersValue as TJSONArray;  for var I := 0 to Users.Count - 1 do  begin    if Users.Items[I] is TJSONObject then      ProcessUser(Users.Items[I] as TJSONObject);  end;endelse  ShowNotification('Unexpected response format.', True);

Network Error Handling#

Wrap REST calls in exception handlers:

pascal
procedure TFormMain.SafeFetch(const EndPoint: string; OnDone: TProc<TJSONObject>);var  StatusCode: Integer;  Response: TJSONObject;begin  try    Response := REST.Get(StatusCode, EndPoint, '');    try      if StatusCode = 200 then        OnDone(Response)      else        HandleResponse(StatusCode, Response);    finally      Response.Free;    end;  except    on E: ENetHTTPClientException do    begin      if E.Message.Contains('SSL') then        ShowNotification('SSL error: check your SSL DLL configuration.', True)      else if E.Message.Contains('Timeout') then        ShowNotification('Request timed out. Check your network.', True)      else        ShowNotification('Network error: ' + E.Message, True);    end;    on E: Exception do      ShowNotification('Error: ' + E.Message, True);  end;end;

User-Friendly Error Messages in HTML#

Display errors in the HTML renderer instead of using ShowMessage dialogs:

pascal
procedure TFormMain.ShowError(const Msg: string);begin  Renderer.SetElementVisible('errorBanner', True);  Renderer.SetElementText('errorBanner', Msg);  Renderer.SetElementStyle('errorBanner', 'background-color', '#e74c3c');  Renderer.SetElementStyle('errorBanner', 'color', 'white');  Renderer.SetElementStyle('errorBanner', 'padding', '12px');  Renderer.SetElementStyle('errorBanner', 'border-radius', '4px');โ€‹  TTask.Run(procedure  begin    Sleep(5000);    TThread.Synchronize(nil, procedure    begin      Renderer.SetElementVisible('errorBanner', False);    end);  end);end;

4. Design-Time vs Runtime#

What to Configure in Object Inspector#

Set these properties at design time -- they rarely change:

ComponentPropertyWhy
TTina4RESTBaseUrlThe API base URL is fixed per deployment
TTina4RESTRequestTina4RESTLinks never change at runtime
TTina4RESTRequestDataKeyJSON key is defined by the API
TTina4RESTRequestMemTableTarget MemTable is fixed
TTina4HTMLPagesRendererRenderer link is permanent
TTina4HTMLRenderCacheEnabledCaching policy is global

What to Configure in Code#

Set these at runtime -- they depend on user actions or environment:

pascal
// Auth tokens change on loginREST.SetBearer(Token);โ€‹// Endpoints change based on contextRESTUsers.EndPoint := '/users/' + UserId + '/orders';โ€‹// Query params change with search/paginationRESTUsers.QueryParams := 'page=2&search=admin';โ€‹// HTML content changes with dataRenderer.HTML.Text := BuildDynamicHTML;โ€‹// Twig variables change per renderPages.SetTwigVariable('userName', CurrentUser.Name);

When to Create Components Dynamically#

Create components at runtime when:

  • The number of instances depends on data (e.g., one TTina4RESTRequest per API entity)
  • The component is temporary (e.g., a one-time report renderer)
  • You need a pool of workers
pascal
function TFormMain.CreateRESTRequest(const EndPoint, DataKey: string;  AMemTable: TFDMemTable): TTina4RESTRequest;begin  Result := TTina4RESTRequest.Create(Self);  Result.Tina4REST := REST;  Result.EndPoint := EndPoint;  Result.DataKey := DataKey;  Result.MemTable := AMemTable;  Result.RequestType := TTina4RequestType.Get;  Result.SyncMode := TTina4RestSyncMode.Clear;end;โ€‹// Usagevar ReqOrders := CreateRESTRequest('/orders', 'records', MemOrders);try  ReqOrders.ExecuteRESTCall;  // process MemOrdersfinally  ReqOrders.Free;end;

5. Data Flow Patterns#

Unidirectional: API to MemTable to UI#

The most common pattern. Data flows in one direction:

API Response --> PopulateMemTableFromJSON --> MemTable --> HTML Render
pascal
procedure TFormMain.LoadProducts;begin  RESTProducts.OnExecuteDone := procedure(Sender: TObject)  begin    TThread.Synchronize(nil, procedure    begin      { MemTable is populated automatically by RESTRequest }      { Now render it }      RenderProductTable;    end);  end;  RESTProducts.ExecuteRESTCallAsync;end;โ€‹procedure TFormMain.RenderProductTable;var  HTML: string;begin  HTML := '<table>';  MemProducts.First;  while not MemProducts.Eof do  begin    HTML := HTML +      '<tr>' +      '  <td>' + MemProducts.FieldByName('name').AsString + '</td>' +      '  <td>' + MemProducts.FieldByName('price').AsString + '</td>' +      '</tr>';    MemProducts.Next;  end;  HTML := HTML + '</table>';  Renderer.HTML.Text := HTML;end;

Bidirectional: UI to API to MemTable to UI#

User fills a form, submits it, the API processes it, the response refreshes the data:

HTML Form --> onclick/OnFormSubmit --> POST to API --> Refresh MemTable --> Re-render
pascal
procedure TFormMain.HandleFormSubmit(Sender: TObject;  const FormName: string; FormData: TStrings);var  StatusCode: Integer;  Body: TJSONObject;  Response: TJSONObject;begin  if FormName = 'productForm' then  begin    Body := TJSONObject.Create;    try      Body.AddPair('name', FormData.Values['name']);      Body.AddPair('price', FormData.Values['price']);โ€‹      Response := REST.Post(StatusCode, '/products', '', Body.ToString);      try        if StatusCode = 201 then        begin          { Refresh the list to include the new product }          RESTProducts.ExecuteRESTCall;          RenderProductTable;          ShowNotification('Product created.');        end        else          ShowError('Failed to create product.');      finally        Response.Free;      end;    finally      Body.Free;    end;  end;end;

Event-Driven: WebSocket to UI#

Real-time updates bypass the request/response cycle:

WebSocket Message --> Parse JSON --> Update MemTable or UI directly
pascal
FWebSocket.OnMessage := procedure(Sender: TObject; const Msg: string)begin  TThread.Synchronize(nil, procedure  var    JSON: TJSONObject;  begin    JSON := StrToJSONObject(Msg);    try      if Assigned(JSON) then      begin        var EventType := JSON.GetValue<string>('type', '');โ€‹        if EventType = 'user.created' then        begin          { Add to MemTable without full refresh }          MemUsers.Append;          MemUsers.FieldByName('id').AsString :=            JSON.GetValue<string>('data.id');          MemUsers.FieldByName('name').AsString :=            JSON.GetValue<string>('data.name');          MemUsers.Post;          RenderUserTable;        end        else if EventType = 'stats.updated' then        begin          { Refresh stats }          FetchStats;        end;      end;    finally      JSON.Free;    end;  end);end;

6. Performance#

MemTable SyncMode for Incremental Updates#

When polling an API for updates, use Sync mode instead of Clear:

pascal
// WRONG for polling -- clears and rebuilds the entire tableRESTUsers.SyncMode := TTina4RestSyncMode.Clear;โ€‹// RIGHT for polling -- updates existing rows, adds new onesRESTUsers.SyncMode := TTina4RestSyncMode.Sync;RESTUsers.IndexFieldNames := 'id';

In Sync mode, the MemTable matches rows by IndexFieldNames. Existing rows are updated in place. New rows are inserted. Bound controls (grids, lists) update smoothly without flickering.

Minimizing HTML Re-renders#

Do not rebuild the entire HTML when only one element changes:

pascal
// WRONG -- rebuilds entire page, resets scroll position, flickersRenderer.HTML.Text := BuildFullPageHTML;โ€‹// RIGHT -- update just the element that changedRenderer.SetElementText('userCount', NewCount.ToString);Renderer.SetElementStyle('statusDot', 'background-color', '#2ecc71');Renderer.SetElementVisible('loadingSpinner', False);

Use SetElementText, SetElementStyle, SetElementVisible, and SetElementValue for surgical updates. Full re-renders should only happen when the page structure changes.

Image Caching#

Enable disk-based caching for images loaded in TTina4HTMLRender:

pascal
Renderer.CacheEnabled := True;Renderer.CacheDir := TPath.Combine(TPath.GetDocumentsPath, 'MyAppCache');

Without caching, every page render re-downloads all images. With caching, images are loaded from disk after the first download. This makes page transitions near-instant.

Lazy Loading with Pagination#

Do not load all records at once. Use pagination:

pascal
procedure TFormMain.FetchPage(APage: Integer);begin  RESTUsers.QueryParams := Format('page=%d&limit=20', [APage]);  RESTUsers.OnExecuteDone := procedure(Sender: TObject)  begin    TThread.Synchronize(nil, procedure    begin      RenderUserTable;      RenderPaginationControls(APage);    end);  end;  RESTUsers.ExecuteRESTCallAsync;end;

Twenty records per page. The user clicks Next to load more. The API does the heavy lifting.


7. Security#

Never Hardcode Credentials#

pascal
// WRONG -- credentials in source codeREST.Username := 'admin';REST.Password := 'P@ssw0rd123';โ€‹// RIGHT -- read from config file or environmentvar Config := TIniFile.Create(TPath.Combine(  TPath.GetDocumentsPath, 'myapp.ini'));try  REST.BaseUrl := Config.ReadString('API', 'BaseUrl', '');  // Do not store credentials in config either  // Use login flow with bearer tokens insteadfinally  Config.Free;end;

Token Refresh Pattern#

Bearer tokens expire. Handle 401 responses with a refresh flow:

pascal
procedure TFormMain.ExecuteWithRefresh(const EndPoint: string;  OnSuccess: TProc<TJSONObject>);var  StatusCode: Integer;  Response: TJSONObject;begin  Response := REST.Get(StatusCode, EndPoint, '');  try    if StatusCode = 401 then    begin      { Try to refresh the token }      var RefreshResponse := REST.Post(StatusCode, '/auth/refresh', '',        '{"refresh_token": "' + FRefreshToken + '"}');      try        if StatusCode = 200 then        begin          FBearerToken := RefreshResponse.GetValue<string>('token');          FRefreshToken := RefreshResponse.GetValue<string>('refresh_token');          REST.SetBearer(FBearerToken);โ€‹          { Retry the original request }          Response.Free;          Response := REST.Get(StatusCode, EndPoint, '');          if StatusCode = 200 then            OnSuccess(Response)          else            DoLogout;        end        else          DoLogout;      finally        RefreshResponse.Free;      end;    end    else if StatusCode = 200 then      OnSuccess(Response)    else      HandleResponse(StatusCode, Response);  finally    Response.Free;  end;end;

HTTPS Only#

Never send credentials or tokens over HTTP. Always use HTTPS:

pascal
// WRONGREST.BaseUrl := 'http://api.example.com';โ€‹// RIGHTREST.BaseUrl := 'https://api.example.com';

If your development server does not have SSL, use a self-signed certificate and configure the HTTP client to accept it during development only.


8. Project Organization#

Separate Data Modules#

Keep REST components and MemTables in a data module, not on the main form:

pascal
// DataModule.pasunit DataModule;โ€‹interfaceโ€‹uses  System.Classes, FireDAC.Comp.Client, Data.DB,  Tina4REST, Tina4RESTRequest;โ€‹type  TDM = class(TDataModule)    REST: TTina4REST;    RESTUsers: TTina4RESTRequest;    RESTProducts: TTina4RESTRequest;    MemUsers: TFDMemTable;    MemProducts: TFDMemTable;  end;โ€‹var  DM: TDM;โ€‹// In MainUnit:uses DataModule;โ€‹procedure TFormMain.LoadUsers;begin  DM.RESTUsers.ExecuteRESTCall;  RenderUserTable(DM.MemUsers);end;

Template File Organization#

Store Twig templates in a structured directory:

templates/  layouts/    base.html              -- Base layout with <head> and structure    sidebar.html           -- Sidebar navigation component  pages/    dashboard.html         -- Dashboard page    users/      list.html            -- User list      detail.html          -- User detail card      form.html            -- User create/edit form  components/    stat-card.html         -- Reusable stat card    data-table.html        -- Reusable data table    notification.html      -- Notification toast

Resource Management#

For deployed applications, embed templates as resources or deploy them alongside the executable:

pascal
procedure TFormMain.FormCreate(Sender: TObject);begin  { Templates are relative to the executable }  Pages.TwigTemplatePath := TPath.Combine(    ExtractFilePath(ParamStr(0)), 'templates');โ€‹  { Or use a configurable path }  var ConfigPath := TPath.Combine(TPath.GetDocumentsPath, 'MyApp');  ForceDirectories(ConfigPath);end;

9. Complete Example: Refactoring a Poorly Written App#

Here is a bad implementation. Read it and find the problems:

pascal
// BAD CODE -- how many issues can you spot?procedure TForm1.Button1Click(Sender: TObject);var  Resp: TJSONObject;  SC: Integer;begin  Tina4REST1.BaseUrl := 'http://api.example.com';  Tina4REST1.Username := 'admin';  Tina4REST1.Password := 'admin123';โ€‹  Resp := Tina4REST1.Get(SC, '/users');  var Users := Resp.GetValue<TJSONArray>('records');  var HTML := '<table>';  for var I := 0 to Users.Count - 1 do  begin    var U := Users.Items[I] as TJSONObject;    HTML := HTML + '<tr><td>' + U.GetValue<string>('name') + '</td></tr>';  end;  HTML := HTML + '</table>';  Tina4HTMLRender1.HTML.Text := HTML;end;

Problems found:

  1. HTTP, not HTTPS -- credentials sent in plain text
  2. Hardcoded credentials -- username and password in source code
  3. BaseUrl set in click handler -- should be set once in FormCreate
  4. No try/finally -- Resp is never freed, memory leak
  5. No nil check on Resp -- crashes if request fails
  6. No status code check -- processes data even on 404 or 500
  7. No exception handling -- network errors crash the app
  8. Synchronous call in UI thread -- freezes the interface
  9. No type checking on JSON values -- crashes on unexpected structure
  10. Component names are defaults -- Button1, Tina4REST1 tell you nothing

Here is the refactored version:

pascal
unit UserListUnit;โ€‹interfaceโ€‹uses  System.SysUtils, System.Classes, System.JSON,  FMX.Types, FMX.Controls, FMX.Forms, FMX.StdCtrls,  Tina4REST, Tina4RESTRequest, Tina4HTMLRender,  FireDAC.Comp.Client;โ€‹type  TFormUserList = class(TForm)    REST: TTina4REST;    RESTUsers: TTina4RESTRequest;    MemUsers: TFDMemTable;    Renderer: TTina4HTMLRender;    ButtonLoad: TButton;    procedure FormCreate(Sender: TObject);    procedure ButtonLoadClick(Sender: TObject);  private    procedure RenderUserTable;    procedure ShowError(const Msg: string);  end;โ€‹implementationโ€‹{$R *.fmx}โ€‹procedure TFormUserList.FormCreate(Sender: TObject);begin  REST.BaseUrl := 'https://api.example.com';  { Token set after login -- never hardcode credentials }โ€‹  RESTUsers.Tina4REST := REST;  RESTUsers.EndPoint := '/users';  RESTUsers.DataKey := 'records';  RESTUsers.MemTable := MemUsers;end;โ€‹procedure TFormUserList.ButtonLoadClick(Sender: TObject);begin  ButtonLoad.Enabled := False;  Renderer.HTML.Text := '<p>Loading...</p>';โ€‹  RESTUsers.OnExecuteDone := procedure(Sender: TObject)  begin    TThread.Synchronize(nil, procedure    begin      ButtonLoad.Enabled := True;โ€‹      if RESTUsers.LastStatusCode = 200 then        RenderUserTable      else if RESTUsers.LastStatusCode = 401 then        ShowError('Please log in first.')      else        ShowError('Failed to load users.');    end);  end;  RESTUsers.ExecuteRESTCallAsync;end;โ€‹procedure TFormUserList.RenderUserTable;var  HTML: string;begin  HTML := '<table style="width: 100%; border-collapse: collapse;">';  HTML := HTML + '<tr style="background: #2c3e50; color: white;">' +    '<th style="padding: 10px;">Name</th></tr>';โ€‹  if MemUsers.Active and (MemUsers.RecordCount > 0) then  begin    MemUsers.First;    while not MemUsers.Eof do    begin      HTML := HTML + '<tr><td style="padding: 10px; border-bottom: 1px solid #eee;">' +        MemUsers.FieldByName('name').AsString + '</td></tr>';      MemUsers.Next;    end;  end  else    HTML := HTML + '<tr><td style="padding: 20px; text-align: center; ' +      'color: #999;">No users found.</td></tr>';โ€‹  HTML := HTML + '</table>';  Renderer.HTML.Text := HTML;end;โ€‹procedure TFormUserList.ShowError(const Msg: string);begin  Renderer.HTML.Text :=    '<div style="padding: 15px; background: #e74c3c; color: white; ' +    '  border-radius: 4px;">' + Msg + '</div>';end;โ€‹end.

What changed:

  • HTTPS, not HTTP
  • No hardcoded credentials -- token set after login
  • BaseUrl set in FormCreate, not in click handler
  • TTina4RESTRequest handles the REST call and MemTable population
  • Async execution with ExecuteRESTCallAsync
  • Status code checking
  • Loading indicator while data fetches
  • Button disabled during load to prevent double-clicks
  • Meaningful component and method names
  • MemTable Active and RecordCount checked before iteration
  • Error messages displayed in the renderer, not as dialogs

10. Exercise: Code Review#

Review the following code and identify five issues. Then fix each one.

pascal
procedure TForm1.LoadOrders;var  R: TJSONObject;  S: Integer;begin  R := Tina4REST1.Get(S, '/orders');  var Data := R.GetValue<TJSONArray>('orders');  PopulateMemTableFromJSON(FDMemTable1, 'orders', R.ToString);โ€‹  var Total := 0.0;  FDMemTable1.First;  while not FDMemTable1.Eof do  begin    Total := Total + FDMemTable1.FieldByName('amount').AsFloat;    FDMemTable1.Next;  end;โ€‹  Tina4HTMLRender1.HTML.Text :=    '<h1>Orders</h1><p>Total: $' + FloatToStr(Total) + '</p>';end;

Solution#

Issue 1: R (TJSONObject) is never freed -- memory leak.

Issue 2: No try/except -- network errors crash the app.

Issue 3: No status code check -- processes a 500 error response as valid data.

Issue 4: FloatToStr uses locale-specific formatting -- on some systems 1234.5 becomes 1234,5.

Issue 5: Synchronous call blocks the UI thread.

Fixed version:

pascal
procedure TFormOrders.LoadOrders;begin  Renderer.HTML.Text := '<p>Loading orders...</p>';โ€‹  RESTOrders.OnExecuteDone := procedure(Sender: TObject)  begin    TThread.Synchronize(nil, procedure    begin      if RESTOrders.LastStatusCode <> 200 then      begin        Renderer.HTML.Text :=          '<div style="color: red;">Failed to load orders.</div>';        Exit;      end;โ€‹      var Total := 0.0;      if MemOrders.Active then      begin        MemOrders.First;        while not MemOrders.Eof do        begin          Total := Total + MemOrders.FieldByName('amount').AsFloat;          MemOrders.Next;        end;      end;โ€‹      Renderer.HTML.Text :=        '<h1>Orders</h1><p>Total: $' +        FormatFloat('#,##0.00', Total) + '</p>';    end);  end;  RESTOrders.ExecuteRESTCallAsync;end;

The TTina4RESTRequest handles JSON parsing and MemTable population. FormatFloat gives consistent formatting. The async call keeps the UI responsive. The status code is checked before processing data.


Summary#

The patterns in this chapter come down to four principles:

  1. Free what you create. Every TJSONObject in a try/finally. Every dynamic component with an owner or explicit Free.
  2. Never block the main thread. Use ExecuteRESTCallAsync with TThread.Synchronize for UI updates.
  3. Check before you use. Status codes before processing responses. Assigned() before accessing objects. Active and RecordCount before iterating MemTables.
  4. Update surgically. Use SetElementText and SetElementVisible instead of rebuilding the entire HTML. Use Sync mode instead of Clear when polling.

These four principles prevent the five most common bugs in Tina4 Delphi applications: memory leaks, frozen UIs, access violations, missing error messages, and flickering displays. Follow them, and the bugs stop before they start.