Zelf
Zelf

Reputation: 2260

Flutter - download files of any type on Android to external storage i.e. Downloads directory

Just about any mobile app framework seems to have the ability to download files to the "Downloads" directory, except for Flutter. Or is it just me?

Since Android SDK 29 it appears Flutter (Android) has taken the direction of only allowing files to be downloaded to the scoped application directory or the application's temporary cache directory. I could be totally wrong on this, which is why I am posting on SO, in the hopes I am.

You can download on Android, but the problem is you would have to write a tutorial for a user to try to find anything you downloaded because of where you can download files in Flutter. Either to the inaccessible except by app applications directory or the impossible to find application temp cache directory.

  1. First question, why is there not a simple way to gain permission and download files on Android to the Downloads or media directory? Or better yet a dialog where the user can pick where they want to download files. I get security but Slack app does it somehow.

On Slack's app I can download a file and it will save to Downloads. Not a big deal.

There is lots of advice on SO about flutter packages that are outdated:

Flutter needs to be able to download files on Android to the Downloads or media directories. There are plenty of legit reasons that you might download a file from a Flutter app that needs to be accessible in external storage that is public and can easily be found through Android's Files app.

Anyway, what options does a Flutter dev have here Oct. 2021 to save files on Android to the Downloads directory??? Btw, just a default on iOS.

Right now, it appears on Android the best option for Flutter devs is to use a something like url_launcher and craft a url that forces a download, which will in turn access the browser's download manager. Is the best scenario for Android right now?

Finally, I love Flutter, but this particular area seems to be needing some love so Flutter devs don't fall behind other frameworks out there.

Upvotes: 4

Views: 6362

Answers (1)

Zelf
Zelf

Reputation: 2260

For Oct 2021 Flutter it turns out there is a great solution for this for Android. The missing package I needed was the flutter_file_dialog package, which is using Kotlin to add the needed programming to save files wherever the Flutter app user wants to on their Android device, including Downloads.

Packages I used:

For android the flow is:

  1. Make sure you have storage permission first.
  2. I'm using GCS and Firebase so I create short lived secure url to private content. Code not included.
  3. Using path_provider getTemporaryDirectory() get the application's scoped cache directory path.
  4. With Dio save the short lived url to temp cache
  5. Then finally, using flutter_file_dialog open a save to dialog and allow the user where they would like to save their downloaded file to.

Code snippets to see flow:

try {
  /// Verify we have storage permissions first.
  /// Code snippet showing me calling my devicePermissions service provider
  /// to request "storage" permission on Android.
  await devicePermissions
      .storagePermissions()
      .then((granted) async {
    /// Get short lived url from google cloud storage for non-public file
    final String? shortLivedUrl...

    if (shortLivedUrl == null) {
      throw Exception('Could not generate a '
          'downloadable url. Please try again.');
    }

    final String url = shortLivedUrl;
    print(url);

    /// Get just the filename from the short lived (super long) url
    final String filename =
        Uri.parse(url).path.split("/").last;
    print('filename: $filename');

    Directory? directory;

    if (Platform.isIOS) {
      directory = await getDownloadsDirectory();
      print(directory?.path);
    } else if (Platform.isAndroid) {
      /// For Android get the application's scoped cache directory
      /// path.
      directory = await getTemporaryDirectory();
    }

    if (directory == null) {
      throw Exception('Could not access local storage for '
          'download. Please try again.');
    }

    print(
        'Temp cache save path: ${directory.path}/$filename');

    /// Use Dio package to download the short lived url to application cache
    await dio.download(
      url,
      '${directory.path}/$filename',
      onReceiveProgress: _showDownloadProgress,
    );

    /// For Android call the flutter_file_dialog package, which will give
    /// the option to save the now downloaded file by Dio (to temp
    /// application cache) to wherever the user wants including Downloads!
    if (Platform.isAndroid) {
      final params = SaveFileDialogParams(
          sourceFilePath: '${directory.path}/$filename');
      final filePath =
          await FlutterFileDialog.saveFile(params: params);

      print('Download path: $filePath');
    }
  });
} on DevicePermissionException catch (e) {
  print(e);
  await OverlayMessage.showAlertDialog(
    context,
    title: 'Need Storage Permission',
    message: devicePermissions.errorMessage,
    barrierDismissible: true,
    leftAction: (context, controller, setState) {
      return TextButton(
        child: Text('Cancel'),
        onPressed: () async {
          controller.dismiss();
        },
      );
    },
    rightAction: (context, controller, setState) {
      return TextButton(
        child: Text('Open App Settings'),
        onPressed: () async {
          controller.dismiss();
          Future.delayed(Duration.zero, () async {
            /// Using permission_handler package we can easily give
            /// the user the option to tap and open settings from the
            /// app and manually allow storage.
            await devicePermissions.openManualAppSettings();
          });
        },
      );
    },
  );
} catch (e) {
  print(e.toString());
}

Upvotes: 4

Related Questions