Search code examples
androidflutterpermissionsstorage

How to save a file in a selected directory in Flutter for Android


I'm trying very hard to understand how to get the permission to write files in a selected directory with File Picker in Android. I'm also using permission handler package. The code is correct, I'm sure. It is something related to permissions in Android Manifest file (but I have already specified read and write external storage permissions).

The dialog in the app for allowing permissions doesn't appear. It directly opens me (as I intended in my code) the setting page to set manually the permission. But here, it is written that the app doesn't require any permission. If I try to write a file in the selected directory, the operation fails and the compiler says to me that this operation isn't allowed, something likes that. Thank you for any help.


Solution

  • The issue you're encountering when trying to save a file to a selected directory in Flutter for Android is due to changes in how Android handles storage permissions, especially starting from Android 10 (API level 29) and above. Even if you've added the read and write permissions in your AndroidManifest.xml, the app might not function as expected because of Scoped Storage and permission requirements.

    Here's how you can resolve this issue:

    Understanding Android Storage Permissions

    • Android 10 (API Level 29): Introduced Scoped Storage, which changes how apps access files on external storage. Apps have limited access to external storage, focusing on app-specific directories.
    • Android 11 (API Level 30) and Above: Further restrictions were applied. The WRITE_EXTERNAL_STORAGE permission is deprecated, and apps are encouraged to use the Storage Access Framework (SAF) or request the MANAGE_EXTERNAL_STORAGE permission (which is highly restricted and requires special approval for Play Store apps).

    Solution Steps

    1. Update Your AndroidManifest.xml

    Ensure that your manifest file has the correct permissions and attributes.

       <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.yourapp">
    
          <!-- Permissions -->
          <uses-permission 
               android:name="android.permission.READ_EXTERNAL_STORAGE" />
    
           <!-- For Android 10 and below -->
          <uses-permission
               android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28" />
    
           <!-- For Android 11 and above (Optional, see notes below) -->
          <uses-permission 
               android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
    
          <application
              android:label="Your App"
              android:icon="@mipmap/ic_launcher"
              android:requestLegacyExternalStorage="true">
              <!-- Other components -->
          </application>
       </manifest>
    

    Notes:

    • android:requestLegacyExternalStorage="true" : Allows your app to use the old storage model on Android 10. This attribute is ignored on
      Android 11 and above.

    • Android 11 (API Level 30) and Above: Further restrictions were applied. The WRITE_EXTERNAL_STORAGE permission is deprecated, and apps are encouraged to use the Storage Access Framework (SAF) or request the MANAGE_EXTERNAL_STORAGE permission (which is highly restricted and requires special approval for Play Store apps).

    1. Request Permissions at Runtime

    Use the permission_handler package to request permissions dynamically.

    import 'package:permission_handler/permission_handler.dart';
    
    Future<void> requestStoragePermission() async {
      var status = await Permission.storage.status;
      if (!status.isGranted) {
        // Request the permission
        if (await Permission.storage.request().isGranted) {
          // Permission granted
        } else {
          // Permission denied
          // You can open app settings to let the user grant the permission
          openAppSettings();
        }
      }
    }
    
    

    Important Always check and request permissions at runtime. Permissions declared in the manifest must also be granted by the user.

    1. Use the Storage Access Framework (SAF)

    Starting from Android 11, it's recommended to use SAF to access files and directories outside of your app's specific storage.

    You can use the storage_access_framework package to interact with SAF.

    Example:

    import 'package:storage_access_framework/storage_access_framework.dart';
    
    Future<void> saveFile() async {
      // Open directory picker
      Uri? uri = await StorageAccessFramework.openDocumentTree();
    
      if (uri == null) {
        // User cancelled the picker
        return;
      }
    
      // The file data you want to save
      final fileName = 'example.txt';
      final mimeType = 'text/plain';
      final data = 'Hello, world!'.codeUnits;
    
      try {
        // Save the file in the selected directory
        await StorageAccessFramework.createFileAsBytes(
          uri,
          fileName,
          mimeType,
          data,
        );
        print('File saved successfully!');
      } catch (e) {
        print('Error saving file: $e');
      }
    }
    

    Benefits of Using SAF:

    • User-Granted Access: The user selects the directory, granting your app access without needing broad storage permissions.
    • No Need for WRITE_EXTERNAL_STORAGE: When using SAF, you don't need the deprecated WRITE_EXTERNAL_STORAGE permission.
    • Scoped Access: Your app only has access to the files and directories the user selects.
    1. Handle Permissions in App Settings

    If your app doesn't request any dangerous permissions, the permissions might not appear in the app settings. Ensure you're requesting the permissions at runtime and handling the user's response appropriately.

    Putting It All Together

    Here's how you can implement the solution in your app:

    Step 1: Update AndroidManifest.xml as shown above.

    Step 2: Request storage permission (optional when using SAF, but good practice):

    await requestStoragePermission();
    

    Step 3: Use SAF to pick the directory and save the file:

    await saveFile();
    

    Full Example:

    import 'package:flutter/material.dart';
    import 'package:permission_handler/permission_handler.dart';
    import 'package:storage_access_framework/storage_access_framework.dart';
    
    class SaveFilePage extends StatefulWidget {
      @override
      _SaveFilePageState createState() => _SaveFilePageState();
    }
    
    class _SaveFilePageState extends State<SaveFilePage> {
      Future<void> _saveFile() async {
        // Request storage permission (optional)
        if (await Permission.storage.request().isGranted) {
          // Proceed with saving the file
          await saveFile();
        } else {
          // Permission denied
          // Optionally, guide the user to app settings
          openAppSettings();
        }
      }
    
      Future<void> saveFile() async {
        // Open directory picker
        Uri? uri = await StorageAccessFramework.openDocumentTree();
    
        if (uri == null) {
          // User cancelled the picker
          return;
        }
    
        // The file data you want to save
        final fileName = 'example.txt';
        final mimeType = 'text/plain';
        final data = 'Hello, world!'.codeUnits;
    
        try {
          // Save the file in the selected directory
          await StorageAccessFramework.createFileAsBytes(
            uri,
            fileName,
            mimeType,
            data,
          );
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('File saved successfully!')),
          );
        } catch (e) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('Error saving file: $e')),
          );
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text('Save File Example'),
            ),
            body: Center(
              child: ElevatedButton(
                onPressed: _saveFile,
                child: Text('Save File'),
              ),
            ));
      }
    }
    

    Additional Tips

    • Check Android Version: Adjust your code to handle different Android versions if necessary.
    if (androidInfo.version.sdkInt >= 30) {
      // Android 11 and above
      // Use SAF
    } else {
      // Android 10 and below
      // You might be able to use traditional methods
    }
    
    • Permissions Not Showing in Settings: If the permissions don't appear in the app settings, ensure you're declaring them in AndroidManifest.xml and requesting them at runtime.
    • Avoid MANAGE_EXTERNAL_STORAGE if Possible: This permission is for specific use cases and might cause your app to be rejected from the Play Store unless justified.
    • Test on Real Devices: Emulators might not accurately reflect permission behaviors. Test on real devices with different Android versions.

    References

    Conclusion

    By updating your app to use the Storage Access Framework and handling permissions correctly, you should be able to save files to a user-selected directory without running into permission issues. This approach aligns with Android's storage policies and ensures your app is future-proof for newer Android versions.