Jump to content
Jean_D

How to save 'zip' file to external SD card?

Recommended Posts

I have a Moto G6 with an 'external' SD card. Because, my device 'internal' storage is running low in space, I thought that I would attempt to write a Delphi app that would allow me to compress the content of my 'internal' Camera folder into a 'zip' archive stored on my 'external' SD card. I understand that I could use the 'built-in' Files manager app to move the files onto my 'external' SD card. But for practice purposes, I decided to try to create my own app.

 

I am using the latest version of Delphi Alexandria (v. 11.2). I am able to access my 'internal' DCIM folder and read its content as well as to add files to a 'zip' file. However, I am unable to 'force' the saving of my 'zip' file to my 'external' SD card. Any input would be greatly appreciated.

Share this post


Link to post
1 hour ago, Jean_D said:

However, I am unable to 'force' the saving of my 'zip' file to my 'external' SD card.

What problem are you having with it, exactly?  Can you be more specific?  What does your code look like that is not working?  Did you grant permission to your app to access the external SD card?  Do you have code that prompts the user if permission has not been granted yet?

Share this post


Link to post
1 hour ago, Remy Lebeau said:

What problem are you having with it, exactly?  Can you be more specific?  What does your code look like that is not working?  Did you grant permission to your app to access the external SD card?  Do you have code that prompts the user if permission has not been granted yet?

Did you grant permission,? Do you have code that prompts the user if permission has not been granted yet?

 

I believe that I do:

procedure TForm_Main.request_Result(const aPermissions: TClassicStringDynArray; const aGrantResults: TClassicPermissionStatusDynArray);
begin
  v_PermissionGranted := ((Length(aGrantResults) = 1) and (aGrantResults[0] = TPermissionStatus.Granted));
end;

procedure TForm_Main.request_Permissions(aPermission: String);
begin
  PermissionsService.RequestPermissions([aPermission], request_Result);
end;
  v_PermissionGranted := PermissionsService.IsPermissionGranted(JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE));

  if not v_PermissionGranted then
    request_Permissions(JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE));

In terms of 'MANAGE_EXTERNAL_STORAGE,' it is only defined within in the manifest file. Is that not enough?

 

My issue is that, when I execute the following code, the 'zip' file is not created on the SD card.

iZipFile := TZipFile.Create;
iZipFile.Open(System.IOUtils.TPath.Combine(System.IOUtils.TPath.GetSharedDocumentsPath, 'Folder-Content.zip'), zmWrite);

 

Share this post


Link to post
18 hours ago, Jean_D said:

I believe that I do:

In case you have to call RequestPermissions(), are you waiting for request_Result() to be called before you attempt to access the SD card?  RequestPermissions() is asynchronous, so your code logic should look something like this:

procedure TForm_Main.SaveZipFile;
var
  iZipFile: TZipFile;
begin
  iZipFile := TZipFile.Create;
  try
    iZipFile.Open(System.IOUtils.TPath.Combine(System.IOUtils.TPath.GetSharedDocumentsPath, 'Folder-Content.zip'), zmWrite);
    //...
  finally
    iZipFile.Free;
  end;
end;

procedure TForm_Main.request_Result(const aPermissions: TClassicStringDynArray;
  const aGrantResults: TClassicPermissionStatusDynArray);
begin
  if (Length(aGrantResults) = 1) and (aGrantResults[0] = TPermissionStatus.Granted) then
    SaveZipFile;
end;

procedure TForm_Main.request_Permissions(aPermission: String);
begin
  PermissionsService.RequestPermissions([aPermission], request_Result);
end;

...

sWriteExternalStorage := JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE);
if PermissionsService.IsPermissionGranted(sWriteExternalStorage) then
  SaveZipFile
else
  request_Permissions(sWriteExternalStorage);

Though, I would probably want to generalize that a bit further, eg:

procedure TForm_Main.SaveZipFile;
var
  iZipFile: TZipFile;
begin
  iZipFile := TZipFile.Create;
  try
    iZipFile.Open(System.IOUtils.TPath.Combine(System.IOUtils.TPath.GetSharedDocumentsPath, 'Folder-Content.zip'), zmWrite);
    // ...
  finally
    iZipFile.Free;
  end;
end;

procedure TForm_Main.doTask_withPermission(const aPermission: String; AProc: TProc);
begin
  if PermissionsService.IsPermissionGranted(aPermission) then begin
    AProc;
  end else
  begin
    PermissionsService.RequestPermissions([aPermission],
      procedure(const aPermissions: TArray<String>;
                const aGrantResults: TArray<TPermissionStatus>);
      begin
        if (Length(aGrantResults) = 1) and (aGrantResults[0] = TPermissionStatus.Granted) then begin
          AProc;
        end;
      end
    );
  end;
end;

...

doTask_withPermission(
  JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE),
  SaveZipFile
);
Quote

In terms of 'MANAGE_EXTERNAL_STORAGE,' it is only defined within in the manifest file. Is that not enough?

No.  It is a permission like any other.  The user has to grant access to it.

Quote

My issue is that, when I execute the following code, the 'zip' file is not created on the SD card.

Are you getting any kind of runtime error about the file not being created?  If not, have you tried looking through the device to see if the file is getting created somewhere else that you are not expecting?  On Android, TPath.GetSharedDocumentsPath() attempts 2 different folder paths - first, it tries to retrieve the user's Documents folder, and if that fails then it retrieves the user's Downloads folder and replaces the last subfolder in the path with a hard-coded 'Documents'.

Edited by Remy Lebeau

Share this post


Link to post
Quote

In case you have to call RequestPermissions(), are you waiting for request_Result()?

Yes, I was. But your solution is more elegant.

 

Quote

No.  It (MANAGE_EXTERNAL_STORAGE) is a permission like any other.  The user has to grant access to it.

Since it is not defined within the 'Androidapi.JNI.Os.pas' file, I didn't think that I had to follow the same process as I did for 'WRITE_EXTERNAL_STORAGE'. How would I do that.

 

Quote

Are you getting any kind of runtime error about the file not being created?

No, I am not getting any error. The application runs 'properly.' The issue is that, instead of storing the 'zip' file within the external SD card, it saves it within the internal 'Documents' folder. Since I am running out of space 'internally,' the compression does not complete.

Share this post


Link to post
16 hours ago, Jean_D said:

Since [MANAGE_EXTERNAL_STORAGE] is not defined within the 'Androidapi.JNI.Os.pas' file, I didn't think that I had to follow the same process as I did for 'WRITE_EXTERNAL_STORAGE'. How would I do that.

Permissions are just string values. The string value of EXTERNAL_MANAGE_STORAGE is 'android.permission.MANAGE_EXTERNAL_STORAGE'.

 

However, granting that permission appears to be different than granting other permissions, per Android's documentation:

Quote

An app can request All files access from the user by doing the following:

  1. Declare the MANAGE_EXTERNAL_STORAGE permission in the manifest.
  2. Use the ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION intent action to direct users to a system settings page where they can enable the following option for your app: Allow access to manage all files.

To determine whether your app has been granted the MANAGE_EXTERNAL_STORAGE permission, call Environment.isExternalStorageManager().

16 hours ago, Jean_D said:

No, I am not getting any error. The application runs 'properly.' The issue is that, instead of storing the 'zip' file within the external SD card, it saves it within the internal 'Documents' folder.

In earlier versions, TPath.GetSharedDocumentsPath() used to call Android's Context.getExternalFilesDir() method, but nowadays it calls Environment.getExternalStoragePublicDirectory() instead.  Maybe Google changed where that latter API's folder is located, compared to the former API?  I don't know.

 

One thing to note from the Context.getExternalFilesDir() documentation:

Quote

If a shared storage device is emulated (as determined by Environment#isExternalStorageEmulated(File)), it's contents are backed by a private user data partition, which means there is little benefit to storing data here instead of the private directories returned by getFilesDir(), etc.

So, you might want to check if your device is emulating shared storage or not.

 

Also, Delphi's Androidapi.IOUtils unit has a public GetExternalDocumentsDir() function, which calls Android's Context.getExternalFilesDir() method.  Maybe try using that function instead of using TPath.GetSharedDocumentsPath().

Share this post


Link to post

IF you want use EXTERNAL_MANAGE_STORAGE and use same code as before (on Android 13 required, introduced in Android 11), your user must enable all access, you can invoke dialog with this code  (working code based on Kastri library)
 

class function TMainForm.NeedsAllFilesAccessPermission: Boolean;
begin
  Result := TOSVersion.Check(11) and not TJEnvironment.JavaClass.isExternalStorageManager;
end;

class function TMainForm.ShowAllFilesAccessPermissionSettings: Boolean;
var
  LIntent: JIntent;
  LUri: Jnet_Uri;
  LAction: JString;
begin
  Result := False;
  if NeedsAllFilesAccessPermission then
  begin
    LUri := TJnet_Uri.JavaClass.fromParts(StringToJString('package'), TAndroidHelper.Context.getPackageName, nil);
    LAction := StringToJString('android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION');
    LIntent := TJIntent.JavaClass.init(LAction, LUri);
    TAndroidHelper.Context.startActivity(LIntent);
    Result := True;
  end;
end;

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×