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?
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 typeTClassicPermissionStatusDynArray
, then I cannot callPermissionsService.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 namedRequestPermissions
.
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.