Android Question What is the current recommended way to copy a file to external storage.

MrKim

Well-Known Member
Licensed User
Longtime User
I am running the latest android (29) my manifest target is 28. This is a B4xPages app.
The goal is to write 1 file to an external directory so the user can access it with other apps. I would like it to work with phones/tablets that are up to 3 or 4 years old.

I tried using FileProvider but it just seems to return File.DirInternal.
File.DirDefaultExternal works but says I should use RuntimePermissions.GetSafeDirDefaultExternal.
I am trying that with CheckAndRequest but it doesn't work. CheckAndRequest DOES WORK with other permissions.
This code:
B4X:
    Dim RP As RuntimePermissions
        RP.CheckAndRequest(RP.PERMISSION_WRITE_EXTERNAL_STORAGE)
        Wait For Activity_PermissionResult(Permission As String, AskResult As Boolean)
Never returns.
The following is written to the manifest:
B4X:
** Activity (main) Pause event (activity is not paused). **
** Activity (main) Resume **
This code in MAIN fires:
B4X:
Sub Activity_PermissionResult (Permission As String, Result As Boolean)
    B4XPages.Delegate.Activity_PermissionResult(Permission, Result)
End Sub
but control never returns to my routine.

In debug, if I put a break on Wait For Activity_PermissionResult(Permission As String, AskResult As Boolean) then
the code in MAIN (B4XPages.Delegate.Activity_PermissionResult(Permission, Result)) never fires and the entire app hangs.

If I use:
B4X:
    If Not(RP.Check(RP.PERMISSION_WRITE_EXTERNAL_STORAGE)) Then
        RP.CheckAndRequest(RP.PERMISSION_WRITE_EXTERNAL_STORAGE)
        Wait For Activity_PermissionResult(Permission As String, AskResult As Boolean)
        .
        .
        .
End IF
It always runs the code inside the If.

I looked here (along with several other places) but didn't see anything useful.
Perhaps I am missing something obvious?
I'm running on a Pixel 3 XL Android Version 10. Build number is QQ3A.200805.001

Here is my manifest in case it is releveant:
B4X:
B4XPages.Delegate.Activity_PermissionResult(Permission, Result)'This code will be applied to the manifest file during compilation.
'You do not need to modify it in most cases.
'See this link for for more information: https://www.b4x.com/forum/showthread.php?p=78136
AddManifestText(
<uses-sdk android:minSdkVersion="5" android:targetSdkVersion="28"/>
<supports-screens android:largeScreens="true"
    android:normalScreens="true"
    android:smallScreens="true"
    android:anyDensity="true"/>)
SetApplicationAttribute(android:icon, "@drawable/icon")
SetApplicationAttribute(android:label, "$LABEL$")
CreateResourceFromFile(Macro, Themes.LightTheme)
'End of default text.

'Added  Kim 9/9/20
AddPermission("android.permission.READ_PHONE_STATE")
AddPermission("android.permission.VIBRATE")
AddPermission("android.permission.CAMERA")
AddPermission("android.permission.FLASHLIGHT")
AddPermission("android.hardware.camera")

'Added  Kim 8/9/20
AddPermission(android.permission.BLUETOOTH)
AddPermission(android.permission.CHANGE_WIFI_MULTICAST_STATE)
'Added 8/19/20 to open pdfs
AddManifestText(<uses-permission
   android:name="android.permission.WRITE_EXTERNAL_STORAGE"
   android:maxSdkVersion="18" />
)

AddApplicationText(
  <provider
  android:name="android.support.v4.content.FileProvider"
  android:authorities="$PACKAGE$.provider"
  android:exported="false"
  android:grantUriPermissions="true">
  <meta-data
  android:name="android.support.FILE_PROVIDER_PATHS"
  android:resource="@xml/provider_paths"/>
  </provider>
)
CreateResource(xml, provider_paths,
   <files-path name="name" path="shared" />
)

As always thanks for your help.
 
Last edited:

agraham

Expert
Licensed User
Longtime User
This is what I do. No manifest entry is needed.
B4X:
Sub Activity_Create(FirstTime As Boolean)
    ' ...
    ' DON'T TARGET ABOVE API 28 FOR THE TIME BEING AS WE NEED TO WORK OUT STORAGE ACCESS
    Dim rp As RuntimePermissions
    rp.CheckAndRequest(rp.PERMISSION_WRITE_EXTERNAL_STORAGE) ' Implicit read capability if granted
    Wait For Activity_PermissionResult (Permission As String, Result As Boolean)
    Log($"PERMISSION_WRITE_EXTERNAL_STORAGE = ${Result}"$)
    ' ...
End Sub

Sub SaveFile(filename As String)
    Dim ostream As OutputStream
    Dim writer As TextWriter
    DesignChanged = False
    B4Sfile = filename
    ostream = File.OpenOutput(Path, filename, False)
    writer.Initialize(ostream)
    writer.Write(TheTextToSave)
    writer.Flush
    writer.Close
End Sub
 
Upvote 0

MrKim

Well-Known Member
Licensed User
Longtime User
This is what I do. No manifest entry is needed.
B4X:
    Dim rp As RuntimePermissions
    rp.CheckAndRequest(rp.PERMISSION_WRITE_EXTERNAL_STORAGE) ' Implicit read capability if granted
    Wait For Activity_PermissionResult (Permission As String, Result As Boolean)
That is exactly what I am doing but it fails as described above. Is there some subtle difference in your code that I am missing? I tried removing the manifest entry but of course that made no difference either. Perhaps it has to do with B4XPages?

Also, I just upgraded to 10.2. It gives a warning now that you SHOULD target 29. So I tried that but it made no difference. Also I am requesting the permission behind the button that actually needs permission, not Activity_Create.
 
Upvote 0

MrKim

Well-Known Member
Licensed User
Longtime User
This is what I do. No manifest entry is needed.
B4X:
Sub Activity_Create(FirstTime As Boolean)
    ' ...
    ' DON'T TARGET ABOVE API 28 FOR THE TIME BEING AS WE NEED TO WORK OUT STORAGE ACCESS
    Dim rp As RuntimePermissions
    rp.CheckAndRequest(rp.PERMISSION_WRITE_EXTERNAL_STORAGE) ' Implicit read capability if granted
    Wait For Activity_PermissionResult (Permission As String, Result As Boolean)
    Log($"PERMISSION_WRITE_EXTERNAL_STORAGE = ${Result}"$)
    ' ...
End Sub

Sub SaveFile(filename As String)
    Dim ostream As OutputStream
    Dim writer As TextWriter
    DesignChanged = False
    B4Sfile = filename
    ostream = File.OpenOutput(Path, filename, False)
    writer.Initialize(ostream)
    writer.Write(TheTextToSave)
    writer.Flush
    writer.Close
End Sub
I tried putting your code in MAINPAGE - result was exactly the same.
I put the code in MAIN and at least it got past the
Wait For
and logged:
PERMISSION_WRITE_EXTERNAL_STORAGE = false
but it did not wait for a result.
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
Go to the Logs tab and press the Permissions button it should show "android.permission. WRITE_EXTERNAL_STORAGE"
If you haven't referenced File.DirRootExternal or File.DirDefaultExternal in your code B4A will not have included the permission.
In that case just put Log(File.DirRootExternal) in your code for the time being.

If it doesn't work then try this B4XPages example. It requests and gets PERMISSION_WRITE_EXTERNAL_STORAGE
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
You have marked this solved. Please update this thread with your solution for the benefit of others who may find this thread when they encounter the same problem.

OK - I see you put it in the first post, but it is might be better to add a new post so things remain in chronological order.
 
Upvote 0

Mahares

Expert
Licensed User
Longtime User
but it is might be better to add a new post so things remain in chronological order.
PERMISSION_WRITE_EXTERNAL_STORAGE
I agree with Andrew. You marked it as 'Solved'. You should let us know with a follow-up post. I had put a solution that I tested on my device and worked, but decided to delete it, because I was puzzled as to what your solution was.
 
Upvote 0

MrKim

Well-Known Member
Licensed User
Longtime User
You have marked this solved. Please update this thread with your solution for the benefit of others who may find this thread when they encounter the same problem.
Will do. But I was evidently too hasty in marking it solved. I am absolutely baffled at this point. I am going to remove the "solved".
 
Upvote 0

MrKim

Well-Known Member
Licensed User
Longtime User
Well, I am baffled. The following code works:

B4X:
    Try
        Dim rp As RuntimePermissions
        rp.CheckAndRequest(rp.PERMISSION_WRITE_EXTERNAL_STORAGE)
        Wait For B4XPage_PermissionResult (Permission As String, Result As Boolean)
        Log(Permission & " : " & Result)
        Log(File.DirRootExternal) ' needed for permission
    
        Dim rp As RuntimePermissions
        If rp.Check(rp.PERMISSION_WRITE_EXTERNAL_STORAGE) = False Then
            MsgboxAsync("In order to share files APAList needs access to external directories. The next dialog will request access. If you say no you will be unable to shar files.", "External Directories")        'explain why this apps needs call permission
            Wait For MsgBox_Result (Result2 As Int)
            Dim rp As RuntimePermissions
            rp.CheckAndRequest(rp.PERMISSION_WRITE_EXTERNAL_STORAGE)
            Wait For B4XPage_PermissionResult (Permission As String, Result As Boolean)
            Log(Permission)
            Log($"PERMISSION_WRITE_EXTERNAL_STORAGE = ${Result}"$)
            If Result = False Then    'user did not give permission
                xui.MsgboxAsync("File cannot be shared if you do not give permission to access directories outside this program.", "Share Aborted")
                Return    'abort dial
            End If
        End If
    
    Catch
        Log(LastException)
        Toast.Show(LastException)
    End Try

Of course it asks twice.

IF I rem the first part:

B4X:
    Try
'        Dim rp As RuntimePermissions
'        rp.CheckAndRequest(rp.PERMISSION_WRITE_EXTERNAL_STORAGE)
'        Wait For B4XPage_PermissionResult (Permission As String, Result As Boolean)
'        Log(Permission & " : " & Result)
'        Log(File.DirRootExternal) ' needed for permission
    
        Dim rp As RuntimePermissions
        If rp.Check(rp.PERMISSION_WRITE_EXTERNAL_STORAGE) = False Then
            MsgboxAsync("In order to share files APAList needs access to external directories. The next dialog will request access. If you say no you will be unable to shar files.", "External Directories")        'explain why this apps needs call permission
            Wait For MsgBox_Result (Result2 As Int)
            Dim rp As RuntimePermissions
            rp.CheckAndRequest(rp.PERMISSION_WRITE_EXTERNAL_STORAGE)
            Wait For B4XPage_PermissionResult (Permission As String, Result As Boolean)
            Log(Permission)
            Log($"PERMISSION_WRITE_EXTERNAL_STORAGE = ${Result}"$)
            If Result = False Then    'user did not give permission
                xui.MsgboxAsync("File cannot be shared if you do not give permission to access directories outside this program.", "Share Aborted")
                Return    'abort dial
            End If
        End If
    
    Catch
        Log(LastException)
        Toast.Show(LastException)
    End Try

Then it fails.

What is worse is that I have tried to duplicate the problem in a simple B4XPages program and I can't. It works fine.

I'm going to bed.
 
Upvote 0

MrKim

Well-Known Member
Licensed User
Longtime User
Well, I got it to work. The only explanation I have is there was some bizarre bit of corruption in the file causing it to compile incorrectly.
Here is the working code. If you can spot a difference between this and the code above that does not work please tell me, I don't see it.

B4X:
    Dim rp As RuntimePermissions
    If rp.Check(rp.PERMISSION_WRITE_EXTERNAL_STORAGE) = False Then
        MsgboxAsync("In order to share files APAList needs access to external directories. The next dialog will request access. If you say no you will be unable to shar files.", "External Directories")        'explain why this apps needs call permission
        Wait For MsgBox_Result (Result2 As Int)
        Dim rp As RuntimePermissions
        rp.CheckAndRequest(rp.PERMISSION_WRITE_EXTERNAL_STORAGE)
        Wait For B4XPage_PermissionResult (Permission As String, Result As Boolean)
        Log(Permission & " : " & Result)
        Log(File.DirRootExternal) ' needed for permission
        Log(Permission)
        Log($"PERMISSION_WRITE_EXTERNAL_STORAGE = ${Result}"$)
        If Result = False Then    'user did not give permission
            xui.MsgboxAsync("File cannot be shared if you do not give permission to access directories outside this program.", "Share Aborted")
            Return   'abort
        End If

For some reason, the SECOND CheckAndRequest (inside the If) did not want to work on its own. After I copied the All of the code around the second CheckAndRequest and put it with the FIRST CheckAndRequest it worked fine. The two lines (CheckAndRequest and Wait For) appear IDENTICAL. Even a search shows them to be identical, but the second one simply wouldn't work.
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
Watch the video tutorial: Runtime Permissions (Android 6.0+ Permissions)

It clearly explains that no permission is required to access rp.GetSafeDirDefaultExternal.
This is why rp.CheckAndRequest failed. It didn't add the permission to the manifest.

Tip: any solution based on rp.PERMISSION_WRITE_EXTERNAL_STORAGE and File.DirRootExternal will not work in a year or two.
 
Upvote 0
Top