Android Question [Solved] ExternalStorage.ListFiles behaving unexpectedly

agraham

Expert
Licensed User
Longtime User
I need to find a known file in a known folder on the external SD Card and display it in an image viewer. Ghost commander can do this apparently using both the old file:// way and also with Stoarge Access Framework.

I am trying to use the ExternalStorage class but it is not behaving as I expect and I'm puzzled. I would expect the following code to produce

Folder = Maps OS 1000K
FindFile =Lat Long UK.jpg
ListFiles =Lat Long UK.jpg

for a known file
/storage/0000-0000/Maps OS 1000K/Lat Long UK.jpg

It doesn't :(

What seems to be happening is that while 'extfolder' seems to have the correct name it is actually referring to a different folder, probably the next in the list and FindFile and ListFiles are looking in this folder and not the expected one. If I set filename to the name of a file in this other folder then it is found by FindFile and ListFile. Or am I being stupid?

B4X:
Sub btnSDCard_Click
   Dim msg As String = ""
   Dim extfolder As ExternalFile
   Dim extfile As ExternalFile
   Dim folder As String = "Maps OS 1000K"
   Dim filename As String = "Lat Long UK.jpg"
  
   Storage.SelectDir(True)
   Wait For Storage_ExternalFolderAvailable
  
   For Each f As ExternalFile In Storage.ListFiles(Storage.Root)
       If f.Name = folder Then
           extfolder = f
           Exit
       End If
   Next
   msg = "Folder = " & extfolder.Name & CRLF
  
   extfile = Storage.FindFile(extfolder, filename)
   msg = msg & "FindFile = " & extfile.Name & CRLF

   For Each g As ExternalFile In Storage.ListFiles(extfolder)
       If g.Name = filename Then
           extfile = g
           msg = msg & "ListFiles = " & g.Name & CRLF
       End If
   Next
   ToastMessageShow(msg, True)  
End Sub

If I could find the file I could copy it to the Download folder in DirRootExternal and open it in an image viewer from there using FileProvider which I have already tested.
 
Last edited:

agraham

Expert
Licensed User
Longtime User
I have run the sample which looks OK which is why what I am seeing is puzzling.

In the code above
For Each f As ExternalFile In Storage.ListFiles(Storage.Root)
f.Name returns this list in this order

Android
Sounds
System Volume Information
Picture
Maps OS Streetview Jpg
Maps OS District pg
AlpineQuest
Maps OS 1000K
eBooks
Maps OS 250K

I'm matching the folder name I want and saving it in 'extfolder' and exiting the loop

When I then invoke

For Each g As ExternalFile In Storage.ListFiles(extfolder)

it is actually iterating the last folder of the list 'Maps OS 250K' even though extfolder.Name contains 'Maps OS 1000K'
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
What is the output of this code:
B4X:
Dim extFolder As ExternalFile = Storage.FindFile(Storage.Root, folder)
Log("Folder found: " & extFolder.IsInitialized)
If extFolder.IsInitialized Then
   Dim extFile As ExternalFile = Storage.FindFile(extFolder, filename)
   Log("File found: " & extFile.IsInitialized)
   If extFile.IsInitialized Then
       Dim rp As RuntimePermissions
       Dim out As OutputStream = File.OpenOutput(rp.GetSafeDirDefaultExternal(""), filename, False)
       File.Copy2(Storage.OpenInputStream(extFile), out)
       out.Close
   End If
End If
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
I already did! Those tutorials are quite useful :) I'm trying to keep as up to date as possible by targeting the latest APIs.

Did you look at my comment in post #4 above? Although the code you posted does what I want I think there is still the original problem present where ExternalFile.Name does not match the folder that ExternalFile.Native iterates.
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
No, by the time it gets to that second loop extfolder already contains the bad ExternalFile pointing to the wrong folder. If you look I already used Exit in the first loop with iterator f and the problem persists. I think the 'problem/feature' is 'ef.Native = DocumentFile' in DocumentFileToExternalFile. I suspect that DocumentFile is being reused somewhere and so always refers to the last items that ListFiles finds
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
This is a workaround for the unexpected behaviour in ListFiles. Each ExternalFile in the returned List has a Native field that does now refer to the expected file or folder.
B4X:
'List all files in the given folder.
' There is a problem here that originally in List "res" the ExternalFile.Native field for all the ExternalFile objects
'   points to the last file of the files() array and not to that expected from the Name and other fields
' Using FindFile seems to overcome this but we can get a null folder, possibly an inaccessible parent, so we ignore it
Public Sub ListFiles (Folder As ExternalFile) As List
   Dim files() As Object = Folder.Native.RunMethod("listFiles", Null)
   Dim res As List
   res.Initialize
   For Each f As JavaObject In files
       'res.Add(DocumentFileToExternalFile(f)) Originally added so but now as follows
       If f.IsInitialized Then res.Add(FindFile(Folder, f.RunMethod("getName", Null)))
   Next
   Return res
End Sub
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
I always like to find out exactly what the root cause of a problems is as it aids my understanding and, as in this case, leads to a better solution.

The real problem here is that the loop in ListFiles "For Each f As JavaObject In files" uses the same JavaObject instance, "f", for each iteration with its internal object set to a different DocumentFile object from the "files" array.

The call within the loop to "DocumentFileToExternalFile(f)" creates a new ExternalFile instance and sets its fields to those of the DocumentFile, however it sets the Native field of the type to a reference to "f".

The result is that all the ExternalFile instances in List "res" have their Native fields pointing to the same JavaObject instance which wraps internally the last DocumentFile instance from the "Files" array.

The solution is to iterate over the actual Objects in the "Files" array and create a new JavaObject for each one as follows

B4X:
Public Sub ListFiles (Folder As ExternalFile) As List
   Dim files() As Object = Folder.Native.RunMethod("listFiles", Null)
   Dim res As List
   res.Initialize
   For Each o As Object In files
       Dim f As JavaObject = o
       res.Add(DocumentFileToExternalFile(f))
   Next
   Return res
End Sub
 
Last edited:
Upvote 0
Top