Try this method that I shared below, which will for sure work on Android 13 or higher recommended by the Flutter development team at Sreyas IT Solutions Pvt Ltd.
The Problem:
While working on the app, we encountered issues when requesting Android storage permissions. These problems occurred particularly on devices running Android 13 or higher. Our app must read and write files for external storage and access media files like images, videos, and audio. With Android 13, Google introduced new permission requirements specifically for accessing media files. This change makes the old approach for requesting storage permissions inadequate.
Here’s what happened:
- Permission Denied Issues: Despite requesting storage permissions via the permission_handler package, the app failed to access external storage. This occurred even though permissions were supposedly granted.
- Scoped Storage & Legacy Behavior: Android 10 introduced scoped storage, which restricted access to shared storage by default. Although I could bypass this by setting requestLegacyExternalStorage=”true”, this didn’t apply to Android 11 and above.
- Android 13 Changes: With Android 13, storage permissions were split into separate categories for images, video, and audio. This change made the traditional READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions insufficient.
The Solution:
To handle these challenges effectively, we updated the AndroidManifest.xml and adjusted the permission-handling logic accordingly. Here’s how we approached it:
1. Updating AndroidManifest.xml for Android 13 Compatibility
For devices running Android 13 or higher, the permissions to access media files were split into specific permissions for images, videos, and audio. This means that we now need to request separate permissions based on the type of media the app accesses.
Here’s the updated AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="your.package.name">
<!-- Permissions for devices running below Android 13 (maxSdkVersion 33) -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="33" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="33" />
<!-- Permissions for Android 13 (API level 33) and above -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<application
android:label="gemfinder"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true" <!-- Retain legacy behavior for Android 10 -->
android:theme="@style/LaunchTheme">
<!-- Activity and other configurations -->
</application>
</manifest>
Key Changes:
- maxSdkVersion=”33″: I set this for READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions to ensure these permissions are only requested on devices running Android 12 and below.
- New Permissions for Android 13+: For Android 13 and above, I added READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, and READ_MEDIA_AUDIO permissions to target specific media types.
2. Handling Permissions in Flutter
To request permissions dynamically based on the Android version, we used the permission_handler package.
Here’s how we handled permissions in my Flutter code:
Future<bool> storagePermission() async {
final DeviceInfoPlugin info = DeviceInfoPlugin(); // import 'package:device_info_plus/device_info_plus.dart';
final AndroidDeviceInfo androidInfo = await info.androidInfo;
debugPrint('releaseVersion : ${androidInfo.version.release}');
final int androidVersion = int.parse(androidInfo.version.release);
bool havePermission = false;
if (androidVersion >= 13) {
final request = await [
Permission.videos,
Permission.photos,
//..... as needed
].request(); //import 'package:permission_handler/permission_handler.dart';
havePermission = request.values.every((status) => status == PermissionStatus.granted);
} else {
final status = await Permission.storage.request();
havePermission = status.isGranted;
}
if (!havePermission) {
// if no permission then open app-setting
await openAppSettings();
}
return havePermission;
}
3. App Settings & Manual Permission Handling
If the user denies the permission or it is permanently denied, we direct them to the app’s settings page, allowing them to manually enable the permissions. The AppSettings.openAppSettings() function from the app_settings package helps with this.
Conclusion:
Handling Android storage permissions for apps can be complex, especially with the changes introduced in newer Android versions. By updating the AndroidManifest.xml and implementing dynamic permission handling based on the Android version, we were able to overcome the challenges of requesting storage and media access permissions.
This solution ensures that the app works across various Android versions, including Android 13 and above, by using the correct permission requests. If you’re building an app that requires storage access, we recommend taking these steps to ensure compatibility with the latest Android changes.