Search code examples
delphifiremonkeydelphi-xe5

delphi XE-5 cross platform development - calling a showmodal form


I am currently developing cross platform applications on my Windows desktop using Delphi XE-5. I have a unit I want to use for cross platform development (for this example, lets just work with Windows and Android).

In both my Windows and Android apps, I have a button with the following onClick event

procedure TfrmMain.Button1Click(Sender: TObject);
begin
LPSystem:=  TLPSystem.Create;
 try
   if LPSystem.execute then
   begin
    ShowMessage('Logged in!');
   end
   else
   ShowMessage('Not logged in!');
 finally
   LPSystem.Free;
 end;
end;

In the execute routine below, a ShowModal dialog is displayed to the user ( a different form depending on platform)

function TLPSystem.execute: Boolean;

var
 {$IFDEF MSWINDOWS}
  frmLoginW: TfrmLoginW;
 {$ENDIF MSWINDOWS}
 {$IFDEF Android}
 zResult: Boolean;
 frmLoginM: TfrmLoginM;
 {$ENDIF Android}
begin
  result:= False;
  {$IFDEF MSWINDOWS}
  frmLoginW:= TfrmLoginW.Create(nil);
  if frmLoginW.ShowModal = mrOK then
    begin
  {$ENDIF MSWINDOWS}

  {$IFDEF Android}
  frmLoginM:= TfrmLoginM.Create(nil);
  frmLoginM.ShowModal (procedure(ModalResult: TModalResult)
  begin
   if ModalResult = mrOK then
  begin
  {$ENDIF Android}


  {$IFDEF MSWINDOWS}
  end;
  frmLoginW.Free;
  {$ENDIF MSWINDOWS}

  {$IFDEF Android}
   end;
   end);
    if zResult then result:= zResult;
    frmLoginM.Free;
   {$ENDIF Android}
end;

This works great for windows, but on Android, I get my result (i.e., the showmessage) before I get the modal dialog box. Not sure why?


Solution

  • Take away your IFDEF statements and look at what you are actually doing:

    Windows:

    function TLPSystem.execute: Boolean;
    var
      frmLoginW: TfrmLoginW;
    begin
      Result := False;
      frmLoginW := TfrmLoginW.Create(nil);
      if frmLoginW.ShowModal = mrOK then
      begin
      end;
      frmLoginW.Free;
    end;
    

    This is functionally OK (though it is not assigning the Result based on the dialog's ModalResult).

    Android:

    function TLPSystem.execute: Boolean;
    var
     zResult: Boolean;
     frmLoginM: TfrmLoginM;
    begin
      Result := False;
      frmLoginM := TfrmLoginM.Create(nil);
      frmLoginM.ShowModal (
        procedure(ModalResult: TModalResult)
        begin
          if ModalResult = mrOK then
          begin
          end;
        end
      );
      if zResult then Result := zResult;
      frmLoginM.Free;
    end;
    

    This is not following the rules outlined in Embarcadero's documentation:

    ShowModal Dialogs in FireMonkey Mobile Apps

    To make matters worse, the overloaded version of ShowModal() that takes an anonymous procedure runs asynchronously, so your Execute() would exit before the modal Form is closed. It cannot wait on the Form before exiting. So you will have to redesign your code to handle that.

    Try something more like this:

    type
      TLPSystemLoginEvent = procedure(LoggedIn: Boolean) of object;
    
      TLPSystem = class
      private
        procedure DoLoginResult(LoggedIn: Boolean);
      public
        OnLoginResult: TLPSystemLoginEvent;
        procedure Execute;
      end;
    
    procedure TLPSystem.DoLoginResult(LoggedIn: Boolean);
    begin
      if Assigned(OnLoginResult) then OnLoginResult(LoggedIn);
    end;
    
    procedure TLPSystem.Execute;
    var
      {$IFDEF MSWINDOWS}
      frmLoginW: TfrmLoginW;
      {$ENDIF}
      {$IFDEF Android}
      frmLoginM: TfrmLoginM;
      LSelf: TLPSystem;
      {$ENDIF}
    begin
      {$IFDEF MSWINDOWS}
      frmLoginW := TfrmLoginW.Create(nil);
      try
        DoLoginResult(frmLoginW.ShowModal = mrOK);
      finally
        frmLoginW.Free;
      end;
      {$ENDIF}
    
      {$IFDEF Android}
      frmLoginM := TfrmLoginM.Create(nil);
      LSelf := Self;
      frmLoginM.ShowModal(
        procedure(ModalResult: TModalResult)
        begin
          LSelf.DoLoginResult(ModalResult = mrOK);
        end
      );
      {$ENDIF}
    end;
    

    procedure TfrmLoginM.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
      Action := TCloseAction.caFree;
    end;
    

    procedure TfrmMain.Button1Click(Sender: TObject);
    var
      LPSystem: TLPSystem;
    begin
      LPSystem := TLPSystem.Create;
      {$IFDEF MSWINDOWS}
      try
      {$ENDIF}
        LPSystem.OnLoginResult := LoginResult;
        LPSystem.Execute;
      {$IFDEF MSWINDOWS}
      finally
        LPSystem.Free;
      end;
      {$ENDIF}
    end;
    
    procedure TfrmMain.LoginResult(LoggedIn: Boolean);
    begin
      if LoggedIn then
        ShowMessage('Logged in!')
      else
        ShowMessage('Not logged in!');
    end;
    

    Alternatively, you can get rid of the try/finally on Windows by utilizing an ARC interface:

    type
      TLPSystemLoginEvent = procedure(LoggedIn: Boolean) of object;
    
      ILPSystem = interface
        procedure Execute;
      end;
    
      TLPSystem = class(TInterfacedObject, ILPSystem)
      private
        fOnLoginResult: TLPSystemLoginEvent;
        procedure DoLoginResult(LoggedIn: Boolean);
      public
        constructor Create(ALoginResult: TLPSystemLoginEvent);
        procedure Execute;
      end;
    
    constructor TLPSystem.Create(ALoginResult: TLPSystemLoginEvent);
    begin
      inherited Create;
      fOnLoginResult := ALoginResult;
    end;
    
    procedure TLPSystem.DoLoginResult(LoggedIn: Boolean);
    begin
      if Assigned(fOnLoginResult) then fOnLoginResult(LoggedIn);
    end;
    
    procedure TLPSystem.Execute;
    var
      {$IFDEF MSWINDOWS}
      frmLoginW: TfrmLoginW;
      {$ENDIF}
      {$IFDEF Android}
      frmLoginM: TfrmLoginM;
      LSelf: ILPSystem;
      {$ENDIF}
    begin
      {$IFDEF MSWINDOWS}
      frmLoginW := TfrmLoginW.Create(nil);
      try
        DoLoginResult(frmLoginW.ShowModal = mrOK);
      finally
        frmLoginW.Free;
      end;
      {$ENDIF}
    
      {$IFDEF Android}
      frmLoginM := TfrmLoginM.Create(nil);
      LSelf := Self;
      frmLoginM.ShowModal(
        procedure(ModalResult: TModalResult)
        begin
          (LSelf as TLPSystem).DoLoginResult(ModalResult = mrOK);
        end
      );
      {$ENDIF}
    end;
    

    procedure TfrmLoginM.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
      Action := TCloseAction.caFree;
    end;
    

    procedure TfrmMain.Button1Click(Sender: TObject);
    var
      LPSystem: ILPSystem;
    begin
      LPSystem := TLPSystem.Create(LoginResult);
      LPSystem.Execute;
    end;
    
    procedure TfrmMain.LoginResult(LoggedIn: Boolean);
    begin
      if LoggedIn then
        ShowMessage('Logged in!')
      else
        ShowMessage('Not logged in!');
    end;