Search code examples
delphifiremonkeydelphi-xe7delphi-10-seattledelphi-10.1-berlin

Delphi delete FireMonkey element from Form on Android


I created an element on my Form with this code in the OnShow event:

procedure TForm4.FormShow(Sender: TObject);
var
  VertScrollLink:TVertScrollBox;
begin
  VertScrollLink := TVertScrollBox.Create(form4);
  VertScrollLink.Align := TAlignLayout.Client;
  VertScrollLink.Parent := form4;
end;

On some action, I need to delete the layout dynamically:

for LIndex := form4.ComponentCount-1 downto 0 do
begin
  if (form4.Components[LIndex].ToString='TVertScrollBox') then
  begin
    //showmessage(form4.Components[LIndex].ToString);
    form4.Components[LIndex].Free;
  end;
end;

This code works good on Windows, but does not delete anything on Android.


Solution

  • The reason is because Delphi uses Automatic Reference Counting for Objects on mobile platforms (iOS and Android), but not on desktop platforms (Windows and OSX). Your Free() is effectively a no-op, because accessing the component from the Components[] property will increment its reference count, and then the Free() will decrement it (in fact, the compiler should have issued a warning about the code having no effect). The component still has active references to it (its Owner and Parent), so it is not actually freed.

    If you want to force the component to be freed, you need to call DisposeOf() on it, eg:

    for LIndex := form4.ComponentCount-1 downto 0 do
    begin
      if form4.Components[LIndex] is TVertScrollBox then
      begin
        form4.Components[LIndex].DisposeOf;
      end;
    end;
    

    Alternatively, remove the active references and let ARC handle the destruction normally:

    var
      VertScrollLink: TVertScrollBox;
      LIndex: Integer;
    begin
      ...
      for LIndex := form4.ComponentCount-1 downto 0 do
      begin
        if form4.Components[LIndex] is TVertScrollBox then
        begin
          VertScrollLink := TVertScrollBox(form4.Components[LIndex]);
          VertScrollLink.Parent := nil;
          VertScrollLink.Owner.RemoveComponent(VertScrollLink);
          VertScrollLink := nil;
        end;
      end;
      ...
    end;
    

    That being said, you might consider keeping track of the component you create so you don't need to use a loop to find it later:

    type
      TForm4 = class(TForm)
        procedure FormShow(Sender: TObject);
        ...
      private
        VertScrollLink: TVertScrollBox;
        ...
      end;
    
    procedure TForm4.FormShow(Sender: TObject);
    begin
      VertScrollLink := TVertScrollBox.Create(Self);
      VertScrollLink.Align := TAlignLayout.Client;
      VertScrollLink.Parent := Self;
    end;
    

    begin
      ...
      if Assigned(VertScrollLink) then
      begin
        VertScrollLink.DisposeOf;
        { or:
        VertScrollLink.Parent := nil;
        VertScrollLink.Owner.RemoveComponent(VertScrollLink);
        }
        VertScrollLink := nil;
      end;
      ...
    end;