I am running into an issue in a Xamarin Forms project I am working on, building and using on UWP and Android (don't care about iOS currently). My app is one which connects to a Bluetooth device to record data, stores it within the app, and exports the data into a .txt file or similar that is used for other purposes outside the app. On Android, targeting either Android 11 or Android 12.
I am working with Xamarin 17.4.0.301, Visual Studio 17.4.0, and .NET Framework 4.8.04084, and Xamarin.Android SDK 13.1.0.1.
My issue is trying to save to a file repeatedly that is accessible. Currently, I use a file hidden in the app's internal storage, using a filepath like this:
string fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "data.txt");
This of course works on both UWP and Android; however, since it is internal, it cannot be accessed by a file explorer on Android. To be seen on Android, I have to export it from the app into a file explorer app, using code like this, where the command is tied to a button press:
OnShareDataCommand = new Command(ShareDataButtonClick);
...
private async void ShareDataButtonClick() {
string fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "data.txt");
await Share.RequestAsync(new ShareFileRequest {
Title = "Share Data",
File = new ShareFile(fileName)
});
}
This requires a user to press the button to export. My desire is for the system to automatically make a file somewhere accessible in the file explorer (like a Downloads folder) and continuously save to it when the function is called. To do this, I have enable access to external storage as such in the Android Manifest:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
But I am running into issues accessing it. The first is when I try to create a filepath that is in external storage like this, based on this StackOverflow question:
var fullPath = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, "file_name.txt");
When I do this, it says Android is not in the context. I imagine this is because I am in Xamarin Forms building for multiple platforms. How can I fix this? How can I access Android's External Storage while in Xamarin Forms? Can I make a platform specific function, and if so, how do I go about it?
This Xamarin forums solution did not work.
I have tried using this line from the External Storage for Xamarin.Android guide, which also does not work because it says Environment does not have the method used:
Environment.GetExternalStoragePublicDirectory(Environment.DirectoryDocuments).AbsolutePath
Any advice? I do not want to make it an Android specific app, as I need the UWP functionality, but I could make a platform-specific save function.
Since Android 11 Read/Write External storage is not allowed, the permission will not be granted. Unless you have an app like a file explorer and even then you have to apply to Google before it will be accepted. So you need to forget about writing to external storage.
See the following links
https://medium.com/swlh/sample-for-android-storage-access-framework-aka-scoped-storage-for-basic-use-cases-3ee4fee404fc
https://www.zoftino.com/how-to-create-browse-files-option-in-android
https://mateuszteteruk.pl/how-to-use-android-storage-access-framework-with-example
https://thedroidlady.com/2020-08-24-android-scoped-storage-demystified
I did it with my app back when I released for Android 11.
If you need any further help just reply here. I should add, I only write Android apps, so I can't help out with anything Forms related or UWP
#ToolmakeSteve, Is your question addressed to me? I'm writing in C# and Xamarin.Android. I don't think a couple of code snippets would provide anything useful. Just follow the links and the link you posted. I'll attempt to provide a test app, but I've not enough time to do that at the moment.
Related
I am trying to write an Android App in Delphi 10.4 (primarily for AndroidTV) using Delphi which can read or write files from/to USB mass storage. I can see the files using FileManager App, but have failed to see them with my App. I found a library on GitHub (Libaums) which would appear to do all I want and much more, but is written in a mixture of Kotlin, Java, and C, which would appear to be very difficult to integrate with Delphi.
Edit: The paths I have tried include: GetDataDirectory(), GetRootDirectory(), GetExternalStorageDirectory(),GetSysExternalStorageDirectory(), GetExternalStoragePublicDirectory(), /mnt/usb001, /mnt/usb002, /mnt/androidwritable, /mnt/user.
The device in mind currently uses SDK 28, but I would like a solution that would still work on later versions.
All my attempts to get file paths report non-existent, which I take to mean I have a permissions problem. Can anyone:
(1) Advise which file path to request?
(2) How to obtain the relevant permissions?
TIA
I have read about and think I understand the essentials of changes in Android 10 and 11. Gone are the days of accessing folders and files outside of the Android app sandbox willy nilly. That's fine. Just need a way forward and that's become difficult.
I have 2+ apps that share a local Sqlite database and related files in a folder. One or more of the apps in the group might be installed - no guarantee on which of the apps are present. On iOS and Windows (UWP) there is a nice "app group" (iOS name for it) style concept that supports this kind of arrangement formally in the platform. First one installed/run will create the local storage files. Last app in the group uninstalled and the OS cleans up the shared storage location. Android never had this concept so a common location was created outside of the app specific sandbox.
After studying the options available going forward, seems like the "Best" option was to use the Storage Access Framework (SAF) to get permission from the user for some common folder to use. Note that although there are many different "sharing" options in Android, none of them are great for this use case, and most are not friendly to cross platform Xamarin C# without wrapping them somehow. This "Best" option using SAF still requires the user to independently pick the SAME folder from each app that wants to share the local db/files. You know users are going to mess that up, but that's beside the point at the moment.
In testing this approach, I have been able to use the SAF picker to get the user to choose a folder. The Documents folder is what I've been choosing to test with as a folder. From there the app attempts to create a subfolder where all this shared "app group" content would go. Unfortunately simply doing a Directory.CreateDirectory(path) gives a System.IO.IOException: 'Read-only file system'. I checked am I am still able to do Directory.CreateDirectory(path) in the app sandbox (GetExternalFilesDir), just not the SAF chosen location.
I am also able to create a directory in the SAF location if I stick to the SAF API, such as illustrated in the Xamarin Android sample here: https://github.com/xamarin/monodroid-samples/blob/master/android5.0/DirectorySelection/DirectorySelectionFragment.cs#L169-L188.
Is there any way to treat the SAF location chosen by the user just like a normal file system and use System.IO operation to manipulate it? The app has been given permission but those ops don't seem to work in that location. Or is there a better overall approach to this problem that I've totally missed?
Normal Java File I/O does not work with Scoped Storage. File paths and File or Directory objects do not worked in Storage Access Framework, you have to do everything through the DocumentFile API. DocumentFile has the ability to create files and directories in locations that the user has granted your app access to through the File-picker dialog.
There IS a way for normal/traditional System.IO file I/O to work after converting the SAF content to a classic file system path. Using the FileUtil logic in this answer https://stackoverflow.com/a/36162691/1735721 I was first able to get permission to a folder from the user:
var intent = new Intent(Intent.ActionOpenDocumentTree);
StartActivityForResult(intent, 1);
The in OnActivityResult(_, _, Intent resultData) use the file util logic:
var folderPath = FileUtil.GetFullPathFromTreeUri(resultData.Data, this);
var filePath = Path.Combine(folderPath, "test.txt");
At that point filePath represents the path and filename in the chosen directory tree, and normal C# System.IO operations are available to the app for that file e.g. StreamWriter and StreamReader.
NOTE: I was creating "test.txt" directly in the chosen folder. This worked to create the file in "A" but then "B" couldn't read that same file (Unauthorized exception). At some point I created a subfolder and "test.txt" was created there instead...then both "A" and "B" could read and write the same file. Unfortunately, a couple days later, I couldn't repeat that. So as it stands this is only a partial solution.
I've tried several folders but looks like no file is created.
External read & write permission have been enabled.
The code run without any sign of error. (as given below)
Here is the screenshot of the code inside mainactivity.cs (this is for testing purposes).
I also assume that since this is platform specific, the code must be in android project.
string filename = Path.Combine(Xamarin.Essentials.FileSystem.AppDataDirectory, "count1.txt") ;
StreamWriter sw = File.CreateText(filename);
sw.WriteLine("asad ini test");
sw.Close();enter code here
1b. I 'm using Android 6.0 phone, but I can't find such folder /data/user...
(I'm not sure if such folder is targeted special version of Android)
I search both internal /Android/myproject/files/ and found nothing
I search external SDcard /Android/myproject/files/ and still found nothing
(please look at the my android phone folders, i can't even find /data folder
Once, this is accomplished the next step would be how to call this function from the generalproject (non android, non IOS, non UWP project).
Example to write file given in xamarin document.
Where should this code reside? in general project folder ? or, android project folder?
The term "external" used in android is very misleading. It doesn't mean the external removable SD card rather some folders that don't require "root" permission. Therefore, initially I can't find the folder which I save a file into.
Another reason is each phone manufacture uses different name, hence it is very difficult to get an absolute address of an external removable SD Card.
More details is explained in blog_external_removable_SD_Card
You can't write to the removable SD Card
an old post here
I've been going over the Android file access documentation lately, but I seem to be unable to figure out how to actually open a file given as a string containing the path to the file I'd like to open.
What I (eventually) want to accomplish is something like this:
The user selects a specific kind of text file using Intents, receiving a URI to the file. From this I derive the path (getPath()) and pass this string to the native C++ code.
The native C++ opens the file from the string, parsing the content.
Perform some actual work with the above.
From what I've found so far, it seems like it is no longer possible to open files this way (as of SDK version 26 at least):
A hard-coded path to a file I know exists gives me permission denied.
The path itself received from getPath() triggers a No such file or directory error.
One workaround called for opening the file on the Java side using the ContentResolver, and then passing the file descriptor to the native side. This works, but it's problematic: the files can contain references to other files to be opened ("include files") making such a solution of limited use.
Just to make things clear, these files reside locally on the "USB" partition of the Android system, unrelated to the app itself. Not as resources/assets to the APK or anything similar which other questions of this kind seem to require.
In summary, I guess the question is this: Is it possible to open a file, and possibly any other files it refers to, given a path from the Java side of the application? Is there any requirements for doing this, such as requesting the correct permissions for folders or something similar?
As of Android 6.0 (API level 23) you need to request permissions every time your app starts for "dangerous actions" such as accessing the filesystem or reading contacts. The linked pages already have a snippet of code you can copy.
On older phones requesting permissions in the manifest was sufficient, but the target SDK version was recenly upped to 8.0 (=26). If you want to support devices pre-6.0, the Android compatibility library will allow you to call the same API.
Is there any folder that can be accessed through a Xamarin.Forms app? The app will be deployed to Android, iOS and as a UWP app. I have a database file and so far, I am using SpecialFolders.Personal, which works for Android and iOS, but not UWP. I would need to get permission for this folder in Windows.
So is there any folder I can use across all 3 devices for which I don't need any special permissions?
Xamarin.Forms has provided Environment.SpecialFolder.LocalApplicationData enum that use to get universal storage path. And it need to be combined with Environment.GetFolderPath method.
var ApplicationDataPath = Environment.GetFolderPath(SpecialFolder.LocalApplicationData);
For more please refer File Handling in Xamarin.Forms