On Android 9.
After permission granted I download a file and try to copy to /storage/emulated/0/Download without success, this is the error:
E/flutter ( 4663): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: FileSystemException: Cannot copy file to '/storage/emulated/0/Download/lorem-ipsum.pdf', path = '/data/user/0/com.example.tester/app_flutter/lorem-ipsum.pdf' (OS Error: Permission denied, errno = 13)
E/flutter ( 4663): #0 _File.copy.
E/flutter ( 4663): #1 _rootRunUnary (dart:async/zone.dart:1132:38)
E/flutter ( 4663): #2 _CustomZone.runUnary (dart:async/zone.dart:1029:19)
E/flutter ( 4663): #3 _FutureListener.handleValue (dart:async/future_impl.dart:126:18)
E/flutter ( 4663): #4 Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:639:45)
E/flutter ( 4663): #5 Future._propagateToListeners (dart:async/future_impl.dart:668:32)
E/flutter ( 4663): #6 Future._completeWithValue (dart:async/future_impl.dart:483:5)
E/flutter ( 4663): #7 Future._asyncComplete.
E/flutter ( 4663): #8 _rootRun (dart:async/zone.dart:1124:13)
E/flutter ( 4663): #9 _CustomZone.run (dart:async/zone.dart:1021:19)
E/flutter ( 4663): #10 _CustomZone.runGuarded (dart:async/zone.dart:923:7)
E/flutter ( 4663): #11 _CustomZone.bindCallbackGuarded.
E/flutter ( 4663): #12 _microtaskLoop (dart:async/schedule_microtask.dart:41:21)
E/flutter ( 4663): #13 _startMicrotaskLoop (dart:async/schedule_microtask.dart:50:5)
Copy file to /storage/emulated/0/Download
Use this as main.dart:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:downloads_path_provider/downloads_path_provider.dart';
import 'package:dio/dio.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:progress_dialog/progress_dialog.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:open_file/open_file.dart';
import 'package:share_extend/share_extend.dart';
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
void main() async {
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
/// Verificar si la app ha sido abierta por una notificaci贸n local agendada
var notificationAppLaunchDetails = await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
print (notificationAppLaunchDetails);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Prueba Descarga',
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Dio dio = Dio();
String _url = "https://www.soundczech.cz/temp/lorem-ipsum.pdf";
Directory _downloadsDirectory;
ProgressDialog pr;
double downloadProgress = 0.0;
GlobalKey _scaffoldKey = GlobalKey();
@override
void initState() {
super.initState();
_initDownloadsDirectoryState();
_initLocalNotifications();
pr = new ProgressDialog(context, ProgressDialogType.Download);
}
Future<void> _initDownloadsDirectoryState() async {
Directory downloadsDirectory;
if (Platform.isAndroid) {
// with this fail:
// downloadsDirectory = await DownloadsPathProvider.downloadsDirectory;
downloadsDirectory = await getApplicationDocumentsDirectory();
} else {
downloadsDirectory = await getApplicationDocumentsDirectory();
}
if (!mounted) return;
setState(() {
_downloadsDirectory = downloadsDirectory;
});
}
void _initLocalNotifications() {
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
var initializationSettingsAndroid = new AndroidInitializationSettings('app_icon');
var initializationSettingsIOS = new IOSInitializationSettings(onDidReceiveLocalNotification: _onDidReceiveLocalNotification);
var initializationSettings = new InitializationSettings(initializationSettingsAndroid, initializationSettingsIOS);
flutterLocalNotificationsPlugin.initialize(initializationSettings, onSelectNotification: _onSelectNotification);
}
Future<dynamic> _onDidReceiveLocalNotification (int id, String title, String body, String payload) {
print(id);
print(title);
print(body);
print(payload);
ShareExtend.share(payload, "file");
return Future.value(payload);
}
Future<dynamic> _onSelectNotification(String payload) async {
print(payload); // payload is the complete file url
OpenFile.open(payload);
return Future.value(payload);
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text('Download test'),
),
body: Center(
child: Container(
child: RaisedButton(
child: Text('Download'),
onPressed: () {
_askForPermission().then(
(_has) async {
if (_has) {
this._download();
}
}
);
},
)
),
),
);
}
Future<bool> _askForPermission() async {
bool result = true;
Map<PermissionGroup, PermissionStatus> permissions;
if (Platform.isAndroid) {
PermissionStatus permission = await PermissionHandler().checkPermissionStatus(PermissionGroup.storage);
if (permission!=PermissionStatus.granted){
permissions = await PermissionHandler().requestPermissions([PermissionGroup.storage]);
result = (permissions[PermissionGroup.storage]==PermissionStatus.granted);
}
}
return Future.value(result);
}
/// Fires file's download
void _download () async {
this.downloadProgress = 0;
if (mounted) { setState(() {}); }
pr.show();
await dio.download(
this._url,
"${this._downloadsDirectory.path}/${this._url.split('/').last}",
options: Options(
headers: {HttpHeaders.acceptEncodingHeader: "*"}
),
onReceiveProgress: (received, total) async {
if (total != -1) {
if (received == total) {
print("Done!");
this.downloadProgress = 0;
pr.update(progress: 100.0, message: "100%");
pr.hide();
Future.delayed(Duration(seconds: 1), () {
try {pr.update(progress: 0.0, message: "0%");} catch(e){}
});
var downloadsDirectory = await DownloadsPathProvider.downloadsDirectory;
String filePath = "${downloadsDirectory.path}/${this._url.split('/').last}";
try {
File("${this._downloadsDirectory.path}/${this._url.split('/').last}").copy(filePath);
} catch(e) {
/// Always enters here
print(e);
}
_notifyCompletedDownload(
"File Downloaded",
"File ${this._url.split('/').last} has been downloaded. Tap to open.",
payload: "${this._downloadsDirectory.path}/${this._url.split('/').last}"
);
} else if (received < total) {
/// Progress
this.downloadProgress = (received / total * 100);
print("${this.downloadProgress.toInt()}%");
pr.update(progress: this.downloadProgress.toInt().toDouble(), message: "${this.downloadProgress.toInt()}%");
}
}
}
);
}
_notifyCompletedDownload(String title, String body, { String payload: "" }) async {
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'777', 'Downloads', 'Notification channel for downloads',
importance: Importance.Max,
priority: Priority.High,
ticker: 'ticker'
);
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(0, title, body, platformChannelSpecifics, payload: payload);
}
}
this is the dependency list:
cupertino_icons: ^0.1.2
permission_handler: ^3.2.2
progress_dialog: ^1.1.0+1
path_provider: ^1.1.2
downloads_path_provider: ^0.1.0
dio: ^2.1.13
flutter_local_notifications: ^0.8.2
share_extend: ^1.0.9
open_file: ^2.0.2
This is the AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.tester">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:name="io.flutter.app.FlutterApplication"
android:label="tester"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<provider
android:name="vn.hunghd.flutterdownloader.DownloadedFileProvider"
android:authorities="${applicationId}.flutter_downloader.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
android:enabled="false"
android:exported="false" />
<provider
android:name="vn.hunghd.flutterdownloader.FlutterDownloaderInitializer"
android:authorities="${applicationId}.flutter-downloader-init"
android:exported="false">
<meta-data
android:name="vn.hunghd.flutterdownloader.MAX_CONCURRENT_TASKS"
android:value="5" />
</provider>
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"></action>
</intent-filter>
</receiver>
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
</application>
</manifest>
Run, click the button, and the error appears.
Version: 1.x
Platform:
I'm running into the same issue, but some of my testers are not. May I ask what Android device you're testing it on?
This is an issue related to actual write permissions on the directory / disk you are trying to write the file too.
This has nothing to do with the user consent to allow an App to write to the disk. Similar issue was solved adding the following attribute to the <application> element in the AndroidManifest.xml file:
android:requestLegacyExternalStorage="true"
More information can also be found here: https://github.com/miguelpruivo/flutter_file_picker/issues/169
This is an issue related to actual write permissions on the directory / disk you are trying to write the file too.
This has nothing to do with the user consent to allow an App to write to the disk. Similar issue was solved adding the following attribute to the
<application>element in the AndroidManifest.xml file:android:requestLegacyExternalStorage="true"More information can also be found here: miguelpruivo/flutter_file_picker#169
Hey but the build fails if the minSdkVersion is not 29
Most helpful comment
This is an issue related to actual write permissions on the directory / disk you are trying to write the file too.
This has nothing to do with the user consent to allow an App to write to the disk. Similar issue was solved adding the following attribute to the
<application>element in the AndroidManifest.xml file:More information can also be found here: https://github.com/miguelpruivo/flutter_file_picker/issues/169