Been trying all day for the best way to create an app specific folder on anroid 11 devices.
I tried /storage/0/emulated/Android/data/com.talkmia.app/files from path_provider plugin as well as /storage/0/emulated/Android/data/com.talkmia.app/ but app data gets cleared after uninstall and i think android:hasFragileData is useless because it'd only prompt if the app was uinstalled the normal way and it also keeps flutter's own extra data.
I did try SAF too with the saf packages but turns out you can't request access to root-storage and it doesn't make sense to ask users to create the folder then grant permission for that folder.
My only option now is to do it just like WhatsApp does it own. Create it in the Android/Media which i'm finding it hard to do. I've read a number of SO posts as well as Github Issues some suggesting MediaStore API while another suggested using the Context::getExternalMediaDirs which would create the app directory in the Android/Media.
Sighs! My Question now is how can get this Context::getExternalMediaDirs done in dart/flutter. I'd really like not to write platform code 😩😩.
Using this package : path_provider
Step 1: Get the commonly used file path
Future<String> getFilePath() async {
Directory appDocumentsDirectory = await getApplicationDocumentsDirectory(); // 1
String appDocumentsPath = appDocumentsDirectory.path; // 2
String filePath = '$appDocumentsPath/demoTextFile.txt'; // 3
return filePath;
}
Step 2: Write content and save the file
void saveFile() async {
File file = File(await getFilePath()); // 1
file.writeAsString("demo file : demoTextFile.txt"); // 2
}
Step 3: Read the file
void readFile() async {
File file = File(await getFilePath()); // 1
String fileContent = await file.readAsString(); // 2
print('File Content: $fileContent');
}
Related
How to do file operations (create/write/update/delete files) using the Flutter "saf" Package.
link:- pub.dev/packages/saf
I didn't find any solutions on Google. I want to use Android "Storage Access Framework" and let users select a folder on External Storage. In this folder, I want to save my app files that users can access.
Previously I was using app-specific storage (Android/data/com.myapp). But from Android 11 users can't access this folder due to Security Update from Google.
Any solutions will be appreciated. Thanks in advance.
bool? isGranted = await saf.getDirectoryPermission(isDynamic: false);
if (isGranted != null && isGranted) {
// Perform some file operations
// **But How?**
} else {
// failed to get the permission
}
First get permission for the directory on which operation to be performed using saf flutter package
import 'package:saf/saf.dart';
await Saf.getDynamicDirectoryPermission()
Files operation in Flutter using dart: io core package
Create
var myFile = File('file.txt');
Read
File('file.txt').readAsString().then((String contents) {
print(contents);
});
Write
final filename = 'file.txt';
var file = await File(filename).writeAsString('some content');
Delete
var myFile = File('file.txt');
myFile.delete();
Source: https://api.dart.dev/stable/2.16.1/dart-io/File-class.html
If you get the folder access by using Storage Access Framework then you can not perform file handling operations on this folder by using the dart: 'dart: io' package.
Check URL: SAF root gives your application access to the corresponding storage, but only through the SAF API, not directly via the filesystem. Also, from Android 11 and above, without MANAGE_EXTERNAL_STORAGE permission, accessing data from external storage using java.io.File is almost completely deprecated.
I found two Packages that support Storage Access Framework in the flutter.
shared_storage: ^0.2.0
saf: ^1.0.3+3
shared_storage 0.2.0 provide us
Uri uri = await openDocumentTree();
List<UriPermission> persistedUris = await persistedUriPermissions();
and
/// Create a new file using the `SAF` API
final newDocumentFile = await createDocumentFile(
mimeType: 'text/plain',
content: 'My Plain Text Comment Created by shared_storage plugin',
displayName: 'CreatedBySharedStorageFlutterPlugin',
directory: anySelectedUriByTheOpenDocumentTreeAPI,
);
print(newDocumentFile);
By above method can create file by using writeAsString() instead of writeAsBytes(). But for creating an Image file we required writeAsBytes().
saf 1.0.3+3 having a method createFileAsBytes() in document_file.dart but not available for usage in current version.
But in the future, both packages will provide these APIs.
Solution:
For now, I have solved this issue by using both packages simultaneously.
Add packages in your pubspec.yaml:
shared_storage: ^0.2.0
saf: ^1.0.3+3
And use the following code
void createFileBySAF(String fileName, Uint8List uint8list) {
/*From: shared_storage: ^0.2.0*/
Uri uri = await openDocumentTree();
/*From: saf: ^1.0.3+3*/
Map<String, dynamic> result = await MethodChannel('com.ivehement.plugins/saf/documentfile').invokeMapMethod<String, dynamic>('createFile', <String, dynamic>{
'mimeType': 'any',
'content': uint8list,
'displayName': fileName,
'directoryUri': '$uri',
});
print(result);
}
For the Update and Delete operations find the usage in https://github.com/ivehement/saf/blob/master/lib/src/storage_access_framework/document_file.dart
checkout this Link
SAF is only use for permission handling. Write, read operation is performed by dart.
Find the correct local path
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Create a reference to the file location
Future get _localFile async {
final path = await _localPath;
return File('$path/counter.txt');
}
** Write data to the file **
Future writeCounter(int counter) async {
final file = await _localFile;
// Write the file
return file.writeAsString('$counter');
}
I want to know how to get write access to a directory path in flutter on API 30 and above. Currently, I cannot find a working complete solution to it anywhere on the internet.
And it would be very grateful if anyone could answer this completely in a detailed way or provide a working sample project.
Note: I know this is not accurately an answer to my question that's why not marked as an answer but this could be useful for those who wanted the "write access" for saving a file in a user-defined location with the native save dialog.
Add flutter_file_dialog plugin in pubspec.yaml. (Version 2.3.0 used in this answer)
Create a function like this.
Future<String?> saveFileInUserDescribedLocation() async {
//Create parameters for save file dialog
//Saving a file through Uint8List data
final params = SaveFileDialogParams(
mimeTypesFilter: ["application/pdf"],
data: Uint8List.fromList(bytes),
fileName: 'Test.pdf');
//You can also use a source file path to save a file through its path like this
//final params = SaveFileDialogParams(
// sourceFilePath: tempPdfPath, mimeTypesFilter: ["application/pdf"]);
//Now provide parameters and save the file.
//It will return the file path chosen by the user to save the file after saving the file.
final filePath = await FlutterFileDialog.saveFile(params: params);
debugPrint("Save filePath: $filePath");
return filePath;
}
Then execute the saveFileInUserDescribedLocation() function. It will provide a native dialog to the user asking the user to choose a location to save and then saves the file.
ElevatedButton(
onPressed: () { await saveFileInUserDescribedLocation(); },
child: const Text("Save File")
)
For the last 2 weeks I've been successfully converting a WPF application to a Xamarin.forms android app. Everything works as intended except for 1 thing. I've been searching the web for a few days for a solution but I can't find a working solution.
This is my current setup:
Xamarin.Froms android app
Target android version: Android 10.0
Minimum android version: Android 7.0
Xamarin.Essentials version: 1.6.0
READ_EXTERNAL_STORAGE permission
WRITE_EXTERNAL_STORAGE permission
My app should be able to open a selected file, using the Xamarin.Essientials FilePicker, Do some stuff and save the File again to the same path. The file selection is handled in the following code:
var pickresult = await FilePicker.PickAsync();
if (pickresult != null)
{
try
{
var result = await pickresult.OpenReadAsync();
var reader = new StreamReader(result);
var jsonstring = reader.ReadToEnd();
ConfigData = JsonConvert.DeserializeObject<ProjectData>(jsonstring);
PLC_Data.ProjectLoaded = true;
L_LoadedProject.Text = pickresult.FileName;
ProjectData.Filepath = pickresult.FullPath;
reader.Close();
result.Close();
//Load login page
B_Next.IsEnabled = true;
}
This is where the problem occurs. Let me clearify what happens step by step:
The FilePicker opens a File picking window.
I navigate to my preferred file in the "download" folder, could be any folder.
The file is read and converted to an object
The file path is saved
when i check the filepath i get the following string:
"/storage/emulated/0/Android/data/com.FAIS.motorconfigapp/cache/2203693cc04e0be7f4f024d5f9499e13/303bc84665374139b6303c03255e9018/config1.json"
So the FilePicker copies the "External" file to the App cache folder and returns the filepath from the Apps cache folder. When i check this folder there is indeed a copy of my selected file inside it. Reading from and writing to this file works as it should.
Now the actual question:
Why does the FilePicker copy my selected file to his cache folder and how can i prevent it?
The user should be able to select a file from anywere on the device and find the updated file in the same location.
I've checked all my settings and in my opinion everyting should be correct so i don't get why a copy is created. Does anyone have an idea?
If you need more settings, pictures, versions please ask.
Thanks in advance
My application records data from a patient monitor and stores it a '*.dat' file in 'Environment.SpecialFolder.Personal'.
The app also allows the user to 'play back' previously acquired recordings.
I wish to include a few sample 'dat' files within the apk. Is it possible to include such files as 'assets' in the project and have them copied to 'Environment.SpecialFolder.Personal' at the time of installation?
Copy the database into Assets folder.
And set the Build Action as AndroidAssect.
You could use the following code to copy the file from Assects folder to Android Application folder.
// Android application default folder.
var appFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
var dbFile = Path.Combine(appFolder, "database.db3");
// Check if the file already exists.
if (!File.Exists(dbFile))
{
using (FileStream writeStream = new FileStream(dbFile, FileMode.OpenOrCreate, FileAccess.Write))
{
// Assets is comming from the current context.
await Assets.Open(databaseName).CopyToAsync(writeStream);
}
}
Download the source file from the link below.
https://github.com/damienaicheh/CopyAssetsProject
Updated:
You could use the code below.
var assetName = "someasset.jpg";
var outputName = Path.Combine(Android.OS.Environment.DirectoryDownloads,
assetName);
using (var assetStream = context.Assets.Open(assetName))
using (var fileStream = new FileStream(outputName, FileMode.CreateNew))
{
await assetStream.CopyToAsync(fileStream);
}
If your device is higher than Android6.0, you need runtime permission.
https://learn.microsoft.com/en-us/samples/xamarin/monodroid-samples/android-m-runtimepermissions/
For more details, please refer to the link below.
How to save file from assets on my device? Xamarin
Dropbox now have an apsolutely new API, which is absolutely differ from the old one (it's interesting why), but there's no ANY actual examples in the internet, so I've found only some code in their examples. Here is it:
// Download the file.
try (OutputStream outputStream = new FileOutputStream (file)) {
mDbxClient.files ()
.download (metadata.getPathLower (), metadata.getRev ())
.download (outputStream);
}
I need to download file from remote folder to the local one, so I need to use this path for example:
.download ("Backups/backup.ab", "/storage/sdcard/Folder/backup.ab")
I've tried it, but get a error
IllegalArgumentException: String 'rev' does not match pattern'
Do you know, what it can be, and metadata.getPathLower () and metadata.getRev () methods are using for? I've learned, that metadata var gets from the first argv from execute (), but what this functions do?
Thanks a lot!
Not sure if if works for android. I have posted the following method just in case someone is looking for a C# .net solution.
private async Task Download(DropboxClient dbx, string folder, string file, string localFilePath)
{
using (var response = await dbx.Files.DownloadAsync(folder + "/" + file))
{
using (var fileStream = File.Create(localFilePath))
{
(await response.GetContentAsStreamAsync()).CopyTo(fileStream);
}
}
}
Parameter example:
file = "YourFileName.pdf";
folder = "/YourDropboxFolderName";
localFilePath = #"C:\Users\YourUserName\YourFileName.pdf";
The Dropbox API v2 Java SDK's download method takes these two parameters:
String path
String rev
Per the download method documentation there, the first is the remote path of the file in Dropbox you want to download, and the second is the identifier for the revision of the file you want. The second parameter is not the local path where you want to save the file, as it appears you're supplying in your code. Instead, you save the file content using the .download (outputStream); portion of the sample code you posted, e.g., as also shown in this sample code.
Also, as stated in the documentation, the second parameter is deprecated and should no longer be used. You can just use the version of the download method that only takes the one parameter. The code for using it is otherwise the same as the sample.
For reference, in the sample, the metadata object is an instance of FileMetadata. You can find more information on the getPathLower and getRev methods in the documentation as well.