androiddelphifiremonkeydelphi-12-athens

How do you call RequestPermissions in Delphi 12.2, without compiler errors?


I am trying to write the following code block which seems to hit a compiler type comparison problem, no matter what I do.

uses
  Fmx.Types,
  System.Permissions,
  androidapi.Helpers, // JStringToString
  androidapi.JNI.JavaTypes,
  androidapi.JNI.Os,  // isExternalStorageManager etc
...

  PermissionsService.RequestPermissions(

        [
        JStringToString(TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE),
        JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE),
        JStringToString(TJManifest_permission.JavaClass.MANAGE_EXTERNAL_STORAGE )
        ],

    {RequestPermission}
      procedure (  const APermissions: TClassicStringDynArray; const AGrantResults: TArray<string> {TClassicPermissionStatusDynArray})
      begin
            if PermissionsService.IsEveryPermissionGranted(AGrantResults) then
               ThenDo;

      end,



    {ExplainReason}
      procedure (const APermissions: TClassicStringDynArray; const APostRationaleProc: TProc)
      begin
       TDialogService.ShowMessage( UserMessage,
        procedure(const AResult: TModalResult)
        begin
          APostRationaleProc;
        end)
      end

  );

The problematic part is this two-line segment:

      procedure (  const APermissions: TClassicStringDynArray; const AGrantResults: TArray<string> {TClassicPermissionStatusDynArray})
      begin
            if PermissionsService.IsEveryPermissionGranted(AGrantResults) then

The question is, what should be the anonymous method's parameter list and types? If I make the AGrantResults be of type TClassicPermissionStatusDynArray, then I cannot call PermissionsService.IsEveryPermissionGranted because of a type mismatch, and if I make it a TArray<string> then the compiler doesn't match any overload for the outer method being invoked named RequestPermissions.

It seems to me like something may be bad in the RTL itself, something broken in Delphi 12.2 that was not broken in earlier versions, or that the compiler itself is failing to recognize that two aliased types are in fact aliases for the same thing, which is TArray<string>.

Is there some way to make this compile and yet call IsEveryPermissionGranted, or should I just rewrite IsEveryPermissionGranted myself, which is just a helper method? Is there something else going on here that I can work around with a different syntax or type naming?


Solution

  • The question is, what should be the anonymous method's parameter list and types?

    You have to match the signatures that are declared in the RTL:

      TRequestPermissionsResultProc = reference to procedure(const APermissions: TClassicStringDynArray;
        const AGrantResults: TClassicPermissionStatusDynArray);
      ...
      TDisplayRationaleProc = reference to procedure(const APermissions: TClassicStringDynArray; const APostRationaleProc: TProc);
    

    If I make the AGrantResults be of type TClassicPermissionStatusDynArray, then I cannot call PermissionsService.IsEveryPermissionGranted because of a type mismatch

    That is because IsEveryPermissionGranted() expects an array of strings, but AGrantResults is instead an array of TPermissionStatus, which is an enum. So, you are forcing a type mismatch by using the wrong parameter in the first place, and declaring it incorrectly.

    You need to call IsEveryPermissionGranted() passing in APermissions instead of AGrantResults, eg:

    PermissionsService.RequestPermissions(
      ...,
    
      {RequestPermission}
      procedure(const APermissions: TClassicStringDynArray;
        const AGrantResults: TClassicPermissionStatusDynArray)
      begin
        if PermissionsService.IsEveryPermissionGranted(TArray<string>(APermissions)) then
          ThenDo;
      end,
    
      ...
    );
    

    However, going back to the OS to re-query if every permission is granted is redundant when the callback already tells you whether they have been granted or not, so use the information you are given, eg:

    PermissionsService.RequestPermissions(
      ...,
    
      {RequestPermission}
      procedure(const APermissions: TClassicStringDynArray;
        const AGrantResults: TClassicPermissionStatusDynArray)
      var
        I: Integer;
      begin
        for I := Low(APermissions) to High(APermissions) do begin
          if AGrantResults[I] <> TPermissionStatus.Granted then Exit;
        end;
        ThenDo;
      end,
    
      ...
    );
    

    and if I make it a TArray<string> then the compiler doesn't match any overload for the outer method being invoked named RequestPermissions.

    Yes, because you are not matching the signature correctly.

    the compiler itself is failing to recognize that two aliased types are in fact aliases for the same thing, which is TArray<string>

    Actually, no. This is not a compiler bug at all, see:

    Type Compatibility and Identity (Delphi)

    Structured Types (Delphi): Array Types and Assignments

    TClassicStringDynArray is NOT an alias for TArray<string>, it is an alias for array of string instead. TArray<string> is also an alias for array of string. But array of string itself is not a type at all, it is a language construction that is being used in the context of a type.

    So, TClassicStringDynArray and TArray<string> are actually separate and distinct types that are not directly compatible with each other (same with TClassicPermissionStatusDynArray and TArray<TPermissionStatus>), per the rules outlined in the documentation above:

    Language constructions that function as type names denote a different type each time they occur.

    Arrays are assignment-compatible only if they are of the same type.

    As the RequestPermissions() callbacks use the TClassic... types, you must declare your anonymous procedures to use those same types. But, IsEveryPermissionGranted() uses TArray<string>, so you will have to explicitly cast from TClassicStringDynArray to TArray<string>, as demonstrated above (and explained in this answer to Any efficient way to convert TArray<string> to TStringDynArray?).

    This mismatch is an RTL bug, not a compiler bug. You should report the issue to Embarcadero. They have been making a lot of progress on standardizing the RTL to use TArray<...> everywhere, but clearly this spot has been missed.