I need to create a app specific folder in storage but error returns "access denied". When i searched about it i found that there is no way to create app specific folder directly in storage from android 11.
Future initRecorder() async {
var directory = await getExternalStorageDirectory();
var toStringConvertedPath=(directory?.path).toString();
print(toStringConvertedPath);
Directory(toStringConvertedPath).create();
}
Currently its saving on :
/storage/emulated/0/Android/data/com.example.survey_app/files
need to save on :
/storage/survey_app/file
Is there any way we can create a folder or there any alternative method to make a folder easily accesible by user in flutter.
SOLVED !!!
What i did is that, i have asked for permission for storage,accessMediaLocation,manageExternalStorage prior to creating the directory.
Future<bool> requestPermission() async {
bool storagePermission = await Permission.storage.isGranted;
bool mediaPermission = await Permission.accessMediaLocation.isGranted;
bool manageExternal = await Permission.manageExternalStorage.isGranted;
if (!storagePermission) {
storagePermission = await Permission.storage.request().isGranted;
}
if (!mediaPermission) {
mediaPermission =
await Permission.accessMediaLocation.request().isGranted;
}
if (!manageExternal) {
manageExternal =
await Permission.manageExternalStorage.request().isGranted;
}
bool isPermissionGranted =
storagePermission && mediaPermission && manageExternal;
if (isPermissionGranted) {
return true;
} else {
return false;
}}
and in androidManifest.xml add these lines.
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Now for creating a directory in storage i.e storage/app_name.
Future initRecorder() async {
bool permission = await requestPermission();
if (Platform.isAndroid) {
if (permission) {
var directory = await getExternalStorageDirectory();
String newPath = "";
print(directory);
String convertedDirectoryPath = (directory?.path).toString();
List<String> paths = convertedDirectoryPath.split("/");
for (int x = 1; x < convertedDirectoryPath.length; x++) {
String folder = paths[x];
if (folder != "Android") {
newPath += "/" + folder;
} else {
break;
}
}
newPath = newPath + "/surveyApp/Audio";
print(newPath);
directory = Directory(newPath);
if (!await directory.exists()) {
await directory.create(recursive: true);
}
} else {
print("permssion denied");
Navigator.of(context).pop();
Fluttertoast.showToast(msg: "Please give neccesary permissions");
return false;
}
}}
Now we can create our app specific directory in the storage level.
Related
I am using :
flutter_sound_lite to record some audio.
and path_provider to get path of my phone.
permission_handler
I am creating directories and a file with a specified path to put my recorded audio.
I wonder if i am not finding it or it doesn't get created.
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter_sound_lite/flutter_sound.dart';
import 'package:flutter_sound_lite/public/flutter_sound_recorder.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
class SoundRecorder {
FlutterSoundRecorder? _audioRecorder;
ModelApiShazam? modelApiShazam;
bool _isRecorderInitialised = false;
bool get isRecording => _audioRecorder!.isRecording;
Future init() async {
_audioRecorder = FlutterSoundRecorder();
final statusMic = await Permission.microphone.request();
if(statusMic != PermissionStatus.granted){
throw RecordingPermissionException('microphone permission');
}
final statusStorage = await Permission.storage.status;
if (!statusStorage.isGranted) {
await Permission.storage.request();
}
await _audioRecorder!.openAudioSession();
directoryPath = await _directoryPath();
completePath = await _completePath(directoryPath);
_createDirectory();
_createFile();
_isRecorderInitialised = true;
}
void dispose(){
if(!_isRecorderInitialised) return;
_audioRecorder!.closeAudioSession();
_audioRecorder = null;
_isRecorderInitialised = false;
}
Future _record() async{
if(!_isRecorderInitialised) return;
print("Path where the file will be : "+completePath);
await _audioRecorder!.startRecorder(
toFile: completePath,
numChannels : 1,
sampleRate: 44100,
);
}
Future _stop() async{
if(!_isRecorderInitialised) return;
var s = await _audioRecorder!.stopRecorder();
File f = File(completePath);
print("The created file : $f");
}
Future toggleRecording() async{
if(_audioRecorder!.isStopped){
await _record();
}else{
await _stop();
}
}
String completePath = "";
String directoryPath = "";
Future<String> _completePath(String directory) async {
var fileName = _fileName();
return "$directory$fileName";
}
Future<String> _directoryPath() async {
var directory = await getApplicationDocumentsDirectory();
var directoryPath = directory.path;
return "$directoryPath/records/";
}
String _fileName() {
return "record.wav";
}
Future _createFile() async {
File(completePath)
.create(recursive: true)
.then((File file) async {
//write to file
Uint8List bytes = await file.readAsBytes();
file.writeAsBytes(bytes);
print("FILE CREATED AT : "+file.path);
});
}
void _createDirectory() async {
bool isDirectoryCreated = await Directory(directoryPath).exists();
if (!isDirectoryCreated) {
Directory(directoryPath).create()
.then((Directory directory) {
print("DIRECTORY CREATED AT : " +directory.path);
});
}
}
}
output excluding flutter_sound :
I/flutter (20652): DIRECTORY CREATED AT : /data/user/0/com.example.shazam/app_flutter/records/
I/flutter (20652): FILE CREATED AT : /data/user/0/com.example.shazam/app_flutter/records/record.wav
I press the button to start the record...
I/flutter (20652): Path where the file will be : /data/user/0/com.example.shazam/app_flutter/records/record.wav
I press the button to end the record...
I/flutter (20652): The created file : File: '/data/user/0/com.example.shazam/app_flutter/records/record.wav'
I don't find where this file is located even if i am following the path
I founs the solution !
Just replace
var directory = await getApplicationDocumentsDirectory();
var directoryPath = directory.path;
by
var directory = await getExternalStorageDirectory();
var directoryPath = directory!.path;
We need to allow users to store files in the external storage and for the same, we use MANAGE_EXTERNAL_STORAGE permission in our application.
Ideally for the android SDK version 30 and above we are using Permission.manageExternalStorage and using Permission.storage for android SDK versions lower than 30 as shown in the below code
// This func is added to access scope storage to export csv files
static Future<bool> externalStoragePermission(BuildContext context) async {
final androidVersion = await DeviceInfoPlugin().androidInfo;
if ((androidVersion.version.sdkInt ?? 0) >= 30) {
return await checkManageStoragePermission(context);
} else {
return await checkStoragePermission(context);
}
}
static Future<bool> checkManageStoragePermission(BuildContext context) async {
return (await Permission.manageExternalStorage.isGranted ||
await Permission.manageExternalStorage.request().isGranted);
}
static Future<bool> checkStoragePermission(BuildContext context,
{String? storageTitle, String? storageSubMessage}) async {
if (await Permission.storage.isGranted ||
await Permission.storage.request().isGranted) {
return true;
} else {
openBottomSheet(
title: storageTitle ?? Str.of(context).storagePermissionRequired,
message: storageSubMessage ?? Str.of(context).storageSubMessage,
).show(context);
return false;
}
}
With the above implementation, everything worked fine while the development and internal release but the Google play console reject the application with the below rejections(Also we have submitted the reason as well for manage_storage permission).
I found and applied the below solution for the above issue in my project.
We have to use SAF(Storage Access Framework) on behalf of the storage permission shared_storage which will allow us to grant the permission for the specific directory in the shared storage and we can store the files over there. I have also added the code sample for the same below.
Future<void> exportFile({
required String csvData,
required String fileName,
}) async {
Uri? selectedUriDir;
final pref = await SharedPreferences.getInstance();
final scopeStoragePersistUrl = pref.getString('scopeStoragePersistUrl');
// Check User has already grant permission to any directory or not
if (scopeStoragePersistUrl != null &&
await isPersistedUri(Uri.parse(scopeStoragePersistUrl)) &&
(await exists(Uri.parse(scopeStoragePersistUrl)) ?? false)) {
selectedUriDir = Uri.parse(scopeStoragePersistUrl);
} else {
selectedUriDir = await openDocumentTree();
await pref.setString('scopeStoragePersistUrl', selectedUriDir.toString());
}
if (selectedUriDir == null) {
return false;
}
try {
final existingFile = await findFile(selectedUriDir, fileName);
if (existingFile != null && existingFile.isFile) {
debugPrint("Found existing file ${existingFile.uri}");
await delete(existingFile.uri);
}
final newDocumentFile = await createFileAsString(
selectedUriDir,
mimeType: AppConstants.csvMimeTypeWhileExport,
content: csvData,
displayName: fileName,
);
return newDocumentFile != null;
} catch (e) {
debugPrint("Exception while create new file: ${e.toString()}");
return false;
}
}
We need to allow users to store files in the external storage and for the same, we use MANAGE_EXTERNAL_STORAGE permission in our application.
You do not need that permission to create your own subdirs in public directories like Download, Documents, DCIM, Pictures and so.
Also you do not need that permission to create files in those directories and subdirs.
I want to download the file in my app and I used Dio, path_provider, permission_handler
so I should write something in the android manifest for android and info for ios.
and I did it.
but it did not work and I've get this error when I clicked on the download button:
I/flutter (11919): Directory: '/storage/emulated/0/Android/data/com.example.podkadeh/files'
I/flutter (11919): FileSystemException: Cannot open file, path = '/storage/emulated/0/podkadeh/Image/image.jpg' (OS Error: Permission denied, errno = 13)
I/flutter (11919): Problem Downloading File
this OS Error: Permission denied, errno = 13
I wrote this lines in manifest:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/>
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage"/>
<uses-permission android:name="android.permission"
and wrote this in Application:
android:requestLegacyExternalStorage="true"
but still, it's not working well and I can't download it
here is my code for download file:
class DownloadImage extends StatefulWidget {
const DownloadImage (
{Key? key,})
: super(key: key);
#override
_DownloadImage State createState() => _DownloadImage State();
}
class _DownloadImageState extends State<DownloadImage > {
Future<bool> saveImageUrl(String url, String fileName) async {
Directory? directory;
try {
if (Platform.isAndroid) {
if (await _requestPermission(Permission.storage)) {
directory = await getExternalStorageDirectory();
String newPath = "";
print(directory);
List<String> paths = directory!.path.split("/");
for (int x = 1; x < paths.length; x++) {
String folder = paths[x];
if (folder != "Android") {
newPath += "/" + folder;
} else {
break;
}
}
newPath = newPath + "/podkadeh" + "/Image";
directory = Directory(newPath);
} else {
return false;
}
} else {
if (await _requestPermission(Permission.photos)) {
directory = await getTemporaryDirectory();
} else {
return false;
}
}
File saveFile = File(directory.path + "/$fileName");
if (!await directory.exists()) {
await directory.create(recursive: true);
}
if (await directory.exists()) {
await dio.download(url, saveFile.path,
onReceiveProgress: (value1, value2) {
setState(() {
progress = value1 / value2;
});
});
if (Platform.isIOS) {
await ImageGallerySaver.saveFile(saveFile.path,
isReturnPathOfIOS: true);
}
return true;
}
return false;
} catch (e) {
print(e);
return false;
}
}
Future<bool> _requestPermission(Permission permission) async {
if (await permission.isGranted) {
return true;
} else {
var result = await permission.request();
if (result == PermissionStatus.granted) {
return true;
}
}
return false;
}
downloadFile() async {
setState(() {
loading = true;
progress = 0;
});
bool downloadedImage = await saveImageUrl(
"https://test.podkadeh.ir/image-cache/Ep-61c9c0152ab37f246dd35a65-500.jpg",
"image.jpg");
if (downloadedImage) {
print("File Downloaded");
} else {
print("Problem Downloading File");
}
setState(() {
loading = false;
});
}
#override
Widget build(BuildContext context) {
return (progress != 0 && progress != 100)
? CircularProgressIndicator(
backgroundColor: loading ? Colors.amber : Colors.black,
valueColor:
AlwaysStoppedAnimation(loading ? Colors.blue : Colors.pink),
strokeWidth: progress,
value: progress,
)
: IconButton(
icon: SvgPicture.asset(MyIcons.frame, color: MyColors.black,height: 20,width: 20,),
onPressed: downloadFile,
padding: const EdgeInsets.all(10),
);
}
}
by the way I use compileSdkVersion 31 and I use this in gradle.properties :
android.useAndroidX=true
android.enableJetifier=true
I solved this problem with the following solution:
instead of just asking for Permission.storage i added two more permission requests Permission.accessMediaLocation , Permission.manageExternalStorage in order to support all versions of Android.
try {
if (Platform.isAndroid) {
if (await _requestPermission(Permission.storage) &&
await _requestPermission(Permission.accessMediaLocation) &&
await _requestPermission(Permission.manageExternalStorage)) {
directory = await getExternalStorageDirectory();
String newPath = "";
print(directory);
.......
the result was:
Performing hot restart...
Syncing files to device Android SDK built for x86...
Restarted application in 2,177ms.
I/flutter ( 3712): Directory: '/storage/emulated/0/Android/data/com.example.untitled/files'
I/flutter ( 3712): File Downloaded
im downloading a pdf file in this address :
/storage/emulated/0/documents/download folder/2021-08-30-16:37:55.544095iH46x.pdf
it works fine in android 10 and below.
but on android 11 i get this error :
FileSystemException: Cannot open file, path = '/storage/emulated/0/documents/download folder/2021-08-30-16:37:55.544095iH46x.pdf' (OS Error: Operation not permitted, errno = 1)
these are my permissions on manifest :
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
these are my permissions on permission handler :
Map<Permission, PermissionStatus> result = await [
Permission.storage,
Permission.manageExternalStorage,
Permission.mediaLibrary].request();
full code :
String fullPath;
try {
Map<Permission, PermissionStatus> result = await [
Permission.storage,
Permission.manageExternalStorage,
Permission.mediaLibrary].request();
if (result[Permission.storage].isGranted && result[Permission.manageExternalStorage].isGranted && result[Permission.mediaLibrary].isGranted ) {
// a custom method for getting path
String dir = await Utility.makeAndGetDownloadFolder();
// preparing access token
final storage = Storage.FlutterSecureStorage();
String accessToken = await storage.read(key: "accessToken");
// generating random string for the pdf file name
String dateNow = DateTime.now().toString();
dateNow = dateNow.replaceAll(' ', '');
String randomName = dateNow + Utility.getRandomString(5);
fullPath = "$dir/$randomName.pdf";
// preparing dio request
String url;
Dio dio = Dio();
Response response;
url = "https://www.example.com/api/downloadFile";
FormData formData = FormData.fromMap({
"idAdvertise": idAdvertise,
});
response = await dio.post(url,
data: formData,
options: Options(
headers: {
HttpHeaders.authorizationHeader: 'Bearer $accessToken',
},
responseType: ResponseType.bytes,
followRedirects: false,
validateStatus: (status) {
return status < 500;
}), onReceiveProgress: (int received, int total) {
if (total != -1) {
setState(() {
_progress = received / total * 100;
});
}
});
if (response.statusCode == 200) {
File file = File(fullPath);
// code reach untill here and then it jumps in catch
var raf = file.openSync(mode: FileMode.write);
raf.writeFromSync(response.data);
await raf.close();
// show notification on success
await showNotification(fullPath);
}else if(response.statusCode == 404){
// status code 404 do something
print("status 404");
}else{
print("status code download is ${response.statusCode}");
Components.customSnackBar(context,AppStr.errDownloadFailed403);
}
setState(() {
});
}
} catch (e) {
print("catch is $e");
}
please dont suggest me using path_provider cause its not what i need
For anyone who still wonders how i fixed the issue,
i used path provider to save the file in the cache and then after saving the file then i moved the file to downloads folder with the flutter_file_dialog package
source code :
//getting the cache directory from path_provider
Directory dir = await getApplicationDocumentsDirectory();
final storage = Storage.FlutterSecureStorage();
String accessToken = await storage.read(key: "accessToken");
var fileName = Utility.generateRandomFileName();
fullPath = "${dir.path}/$fileName.pdf";
bool checkFileExist = true;
while (checkFileExist) {// if file already exists with this name then we generate different name
if (await File(fullPath).exists()) {
fileName = Utility.generateRandomFileName();
fullPath = "${dir.path}/$fileName.pdf";
} else {
checkFileExist = false;
}
}
Dio dio = Dio();
Response response;
FormData formData;
formData = FormData.fromMap({
"id": widget.id,
});
response = await dio.post('https://example.com/downloadfile',
data: formData,
options: Options(
headers: {
HttpHeaders.authorizationHeader: 'Bearer $accessToken',
},
responseType: ResponseType.bytes,
followRedirects: false,
validateStatus: (status) {
return status < 500;
}),
onReceiveProgress: (int received, int total) {
if (total != -1) {
setState(() {
_progressDownloading = received / total * 100;
});
}
});
if (response.statusCode == 200) {
File file = File(fullPath);
var raf = file.openSync(mode: FileMode.write);
raf.writeFromSync(response.data);
await raf.close();
// moving the file from cache to anywhere you want
final params = SaveFileDialogParams(sourceFilePath: fullPath);
await FlutterFileDialog.saveFile(params: params).then((value) async {
if(value != null){//if the file saved successfully
await showNotification();
}else{
print('error couldnt move the pdf file');
}
});
file.delete();//deleting the pdf from cache
}else{
print('something went wrong');
}
I'm creating a flutter app where I want to download and store an image to the external storage (not documents directory) so it can be viewed by any photo gallery app.
I'm using the following code to create a directory
var dir = await getExternalStorageDirectory();
if(!Directory("${dir.path}/myapp").existsSync()){
Directory("${dir.path}/myapp").createSync(recursive: true);
}
It's giving me following error:
FileSystemException: Creation failed, path = '/storage/emulated/0/myapp' (OS Error: Permission denied, errno = 13)
I have set up permissions in the manifest file and using the following code for runtime permissions
List<Permissions> permissions = await Permission.getPermissionStatus([PermissionName.Storage]);
permissions.forEach((p) async {
if(p.permissionStatus != PermissionStatus.allow){
final res = await Permission.requestSinglePermission(PermissionName.Storage);
print(res);
}
});
I have verified in settings that app has got the permission, also as suggested on some answers here I've also tried giving permission from settings app manually which did not work.
You need to request permissions before saving a file using getExternalStorageDirectory.
Add this to Androidmanifest.xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Then use the permission_handler package to get Storage permission:
https://pub.dev/packages/permission_handler
If you are running in the Emulator, getExternalStorageDirectory returns:
/storage/emulated/0/Android/data/com.example.myapp/files/
If you just need the external dir to create a directory under it, then just use:
/storage/emulated/0/
You can create a directory under this folder, so they user will be able to open the files.
The below code is working fine in my application to download an image using the url to the external storage
Future<bool> downloadImage(String url) async {
await new Future.delayed(new Duration(seconds: 1));
bool checkResult =
await SimplePermissions.checkPermission(Permission.WriteExternalStorage);
if (!checkResult) {
var status = await SimplePermissions.requestPermission(
Permission.WriteExternalStorage);
if (status == PermissionStatus.authorized) {
var res = await saveImage(url);
return res != null;
}
} else {
var res = await saveImage(url);
return res != null;
}
return false;
}
Future<Io.File> saveImage(String url) async {
try {
final file = await getImageFromNetwork(url);
var dir = await getExternalStorageDirectory();
var testdir =
await new Io.Directory('${dir.path}/iLearn').create(recursive: true);
IM.Image image = IM.decodeImage(file.readAsBytesSync());
return new Io.File(
'${testdir.path}/${DateTime.now().toUtc().toIso8601String()}.png')
..writeAsBytesSync(IM.encodePng(image));
} catch (e) {
print(e);
return null;
}
}
Future<Io.File> getImageFromNetwork(String url) async {
var cacheManager = await CacheManager.getInstance();
Io.File file = await cacheManager.getFile(url);
return file;
}
Namespaces
import 'dart:io' as Io;
import 'package:image/image.dart' as IM;
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:path_provider/path_provider.dart';
import 'package:simple_permissions/simple_permissions.dart';
Hope it helps
here is an operational and 100% Dart code to make things easier :
import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:path/path.dart' as Path;
import 'package:path_provider/path_provider.dart';
///getExternalStoragePublicDirectory
enum extPublicDir {
Music,
PodCasts,
Ringtones,
Alarms,
Notifications,
Pictures,
Movies,
Download,
DCIM,
Documents,
Screenshots,
Audiobooks,
}
/// use in loop or without:
/// generation loop of a creation of the same directory in a list
/// public or shared folders by the Android system
/*
for (var ext in extPublicDir.values) {
ExtStorage.createFolderInPublicDir(
type: ext, //or without loop : extPublicDir.Download,
folderName: "folderName", // folder or folder/subFolder/... to create
);
}
*/
/// provided the ability to create folders and files within folders
/// public or shared from the Android system
///
/// /storage/emulated/0/Audiobooks
/// /storage/emulated/0/PodCasts
/// /storage/emulated/0/Ringtones
/// /storage/emulated/0/Alarms
/// /storage/emulated/0/Notifications
/// /storage/emulated/0/Pictures
/// /storage/emulated/0/Movies
/// storage/emulated/0/Download
/// /storage/emulated/0/DCIM
/// /storage/emulated/0/Documents
/// /storage/emulated/0/Screenshots //Screenshots dropping ?
/// /storage/emulated/0/Music/
class ExtStorage {
//According to path_provider
static Future<String> get _directoryPathESD async {
var directory = await getExternalStorageDirectory();
if (directory != null) {
log('directory:${directory.path}');
return directory.path;
}
log('_directoryPathESD==null');
return '';
}
/// create or not, but above all returns the created folder in a public folder
/// official, folderName = '', only return the public folder: useful for
/// manage a file at its root
static Future<String> createFolderInPublicDir({
required extPublicDir type,
required String folderName,
}) async {
var _appDocDir = await _directoryPathESD;
log("createFolderInPublicDir:_appDocDir:${_appDocDir.toString()}");
var values = _appDocDir.split("${Platform.pathSeparator}");
values.forEach(print);
var dim = values.length - 4; // Android/Data/package.name/files
_appDocDir = "";
for (var i = 0; i < dim; i++) {
_appDocDir += values[i];
_appDocDir += "${Platform.pathSeparator}";
}
_appDocDir += "${type.toString().split('.').last}${Platform.pathSeparator}";
_appDocDir += folderName;
log("createFolderInPublicDir:_appDocDir:$_appDocDir");
if (await Directory(_appDocDir).exists()) {
log("createFolderInPublicDir:reTaken:$_appDocDir");
return _appDocDir;
} else {
log("createFolderInPublicDir:toCreate:$_appDocDir");
//if folder not exists create folder and then return its path
final _appDocDirNewFolder =
await Directory(_appDocDir).create(recursive: true);
final pathNorma = Path.normalize(_appDocDirNewFolder.path);
log("createFolderInPublicDir:ToCreate:pathNorma:$pathNorma");
return pathNorma;
}
}
}
For Android 11 and higher, android change his policy so you have add permission at AndroidManifest..
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
And Also add these two line inside <application ......
android:requestLegacyExternalStorage="true"
android:preserveLegacyExternalStorage="true"