Flutter[Android]:无法发布具有管理外部存储权限的应用程序

发布于 2025-01-19 14:16:35 字数 1607 浏览 4 评论 0原文

我们需要允许用户将文件存储在外部存储中,为此,我们在应用程序中使用MANAGE_EXTERNAL_STORAGE权限。

理想情况下,对于 Android SDK 版本 30 及更高版本,我们使用 Permission.manageExternalStorage ,对于 Android SDK 版本低于 30 的版本,我们使用 Permission.storage ,如下面的代码

  // 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;
    }
  }

所示实施中,在开发和内部发布时一切正常,但 Google Play 控制台拒绝了该应用程序,并出现以下拒绝(我们还提交了原因以及管理存储权限)。

输入图像描述这里

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).

enter image description here

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

冷血 2025-01-26 14:16:35

我找到了此问题的解决方案,

在低于 11 或高于 11 的 Android 级别中进行了正确测试。

首先请检查 onpressd 按钮上的 Android 设备是否像这样

if(Platform.isAndroid)
{
  downloadAndSavePDF('your network link');
}

=>;然后是方法

Future<void> downloadAndSavePDF(String pdfUrl, String fileName) async {
final response = await http.get(Uri.parse(pdfUrl));
if (response.contentLength == 0) {
  return Future.value(null);
}
try {
  if (response.statusCode == 200) {
    // Get the downloads directory
    Directory downloadsDirectory =
        Directory('/storage/emulated/0/Download/');
    final File file = File('${downloadsDirectory.path}/$fileName');
    if (file.existsSync()) {
      Navigator.pop(context);
      CustomSnakeBar(errorMessage: 'File already downloaded')
          .snakeBar(context);
      return;
    }
    // Write the PDF content to the file
    await file.writeAsBytes(response.bodyBytes);
    Navigator.pop(context);
    CustomSnakeBar(
            errorMessage: 'PDF downloaded', colorText: ConstColor.green)
        .snakeBar(context);
  } else {
    CustomSnakeBar(errorMessage: ConstString.somethingWentWrong)
        .snakeBar(context);
  }
} catch (e) {
  Navigator.pop(context);
  CustomSnakeBar(errorMessage: ConstString.somethingWentWrong)
      .snakeBar(context);
}
}

I found solution for this issue

properly tested in android level less then 11 or greater then 11

First please check this is that android device on your onpressd button like this

if(Platform.isAndroid)
{
  downloadAndSavePDF('your network link');
}

=> and then method

Future<void> downloadAndSavePDF(String pdfUrl, String fileName) async {
final response = await http.get(Uri.parse(pdfUrl));
if (response.contentLength == 0) {
  return Future.value(null);
}
try {
  if (response.statusCode == 200) {
    // Get the downloads directory
    Directory downloadsDirectory =
        Directory('/storage/emulated/0/Download/');
    final File file = File('${downloadsDirectory.path}/$fileName');
    if (file.existsSync()) {
      Navigator.pop(context);
      CustomSnakeBar(errorMessage: 'File already downloaded')
          .snakeBar(context);
      return;
    }
    // Write the PDF content to the file
    await file.writeAsBytes(response.bodyBytes);
    Navigator.pop(context);
    CustomSnakeBar(
            errorMessage: 'PDF downloaded', colorText: ConstColor.green)
        .snakeBar(context);
  } else {
    CustomSnakeBar(errorMessage: ConstString.somethingWentWrong)
        .snakeBar(context);
  }
} catch (e) {
  Navigator.pop(context);
  CustomSnakeBar(errorMessage: ConstString.somethingWentWrong)
      .snakeBar(context);
}
}
安穩 2025-01-26 14:16:35

我在我的项目中找到并应用了以下解决方案来解决上述问题。

  1. 我们必须使用 SAF(存储访问框架) 代表存储权限 shared_storage 这将允许我们授予特定目录的权限共享存储,我们可以将文件存储在那里。我还添加了下面相同的代码示例。
  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;
    }
  }

I found and applied the below solution for the above issue in my project.

  1. 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;
    }
  }
花辞树 2025-01-26 14:16:35

如果不是需要访问设备外部存储的应用程序(例如Antivirus应用程序),并且您想在flutter中下载文件,则可以只使用软件包 path_provider 在安装过程中使用Android使用您的应用特定目录提供商。使用它以下载文件

final response = await http.get(Uri.parse(URL!));
final appDir = await getApplicationDocumentsDirectory();
File file = File(appDir.path + '/${filename}');
try {
  await file.writeAsBytes(response.bodyBytes);
  print('success');
} catch (e) {
  print(e.toString());
}

,或者您可以使用 dio package package downlaod downlaod它管理的文件管理您需要创建的一切盒子里的文件为您

try {
  Response response = await Dio().download(
    downloadURL!,
    appDir.path + '/${filename}',
    onReceiveProgress: (received, total) {
      // You can handle download progress here
      print('Received: $received, Total: $total');
    },
  );
  print('success');
} catch (e) {
  print('Error: $e');
}

If is not an app that needs the to access the device external storage (e.g antivirus app) and you want to download a file in Flutter, you might just use the package path_provider to use your app specific directory provider by android during installation. use this to download file

final response = await http.get(Uri.parse(URL!));
final appDir = await getApplicationDocumentsDirectory();
File file = File(appDir.path + '/${filename}');
try {
  await file.writeAsBytes(response.bodyBytes);
  print('success');
} catch (e) {
  print(e.toString());
}

or you can use dio package to downlaod the file it manages everything you need to create the file out of the box for you

try {
  Response response = await Dio().download(
    downloadURL!,
    appDir.path + '/${filename}',
    onReceiveProgress: (received, total) {
      // You can handle download progress here
      print('Received: $received, Total: $total');
    },
  );
  print('success');
} catch (e) {
  print('Error: $e');
}
别理我 2025-01-26 14:16:35

<块引用>

我们需要允许用户将文件存储在外部存储中,为此,我们在应用程序中使用 MANAGE_EXTERNAL_STORAGE 权限。

您不需要该权限即可在公共目录(例如下载、文档、DCIM、图片等)中创建自己的子目录。

此外,您不需要该权限即可在这些目录和子目录中创建文件。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文