Flutter应用内权限请求插件in_app_permission的使用

发布于 1周前 作者 caililin 来自 Flutter

Flutter应用内权限请求插件in_app_permission的使用

在大多数操作系统上,权限不会在安装应用程序时直接授予。相反,开发者需要在应用程序运行时向用户请求权限。本插件提供了一个跨平台(iOS、Android)的API来请求权限并检查其状态。你还可以打开设备的应用设置,以便用户可以授予权限。在Android上,你可以显示请求权限的理由。

安装与配置

Android(点击展开)

升级预1.12 Android项目

自版本4.4.0起,该插件使用了Flutter 1.12的Android插件API。不幸的是,这意味着应用开发者也需要将他们的应用迁移到支持新的Android基础设施。你可以通过遵循升级预1.12 Android项目迁移指南进行迁移。如果不这样做,可能会导致意外行为。最常见的已知错误是在调用request()方法后,permission_handler未返回。

AndroidX

自版本3.1.0起,permission_handler插件切换到了AndroidX版本的Android支持库。这意味着你需要确保你的Android项目也升级到支持AndroidX。详细说明可以在这里找到。

简化版说明如下:

  1. 在你的gradle.properties文件中添加以下内容:
    android.useAndroidX=true
    android.enableJetifier=true
    
  2. 确保你在android/app/build.gradle文件中的compileSdkVersion设置为33:
    android {
      compileSdkVersion 33
      ...
    }
    
  3. 确保你将所有android.依赖项替换为其AndroidX对应项(完整的列表可以在这里找到)。

AndroidManifest.xml文件中添加权限。通常,只需在main版本中添加即可。

<!-- 这里是一个示例 AndroidManifest.xml 文件 -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <!-- 其他权限... -->
    <application
        android:label="myapp"
        android:icon="@mipmap/ic_launcher">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>
iOS(点击展开)

Info.plist文件中添加权限。

<!-- 这里是一个示例 Info.plist 文件 -->
<key>NSCameraUsageDescription</key>
<string>我们需要访问您的相机以完成此操作。</string>
<key>NSMicrophoneUsageDescription</key>
<string>我们需要访问您的麦克风以完成此操作。</string>
<!-- 其他权限... -->

重要提示:在提交应用时,必须包含所有可能的权限选项。这是因为permission_handler插件会触及所有不同的SDK,并且静态代码分析器(由Apple在应用提交时运行)会检测到这一点,并会在找不到匹配的权限选项时断言。

Podfile文件中添加以下内容:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)

    target.build_configurations.each do |config|
      config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
        '$(inherited)',
        'PERMISSION_EVENTS=1',
        'PERMISSION_REMINDERS=1',
        'PERMISSION_CONTACTS=1',
        'PERMISSION_CAMERA=1',
        'PERMISSION_MICROPHONE=1',
        'PERMISSION_SPEECH_RECOGNIZER=1',
        'PERMISSION_PHOTOS=1',
        'PERMISSION_LOCATION=1',
        'PERMISSION_LOCATION_WHENINUSE=0',
        'PERMISSION_NOTIFICATIONS=1',
        'PERMISSION_MEDIA_LIBRARY=1',
        'PERMISSION_SENSORS=1',
        'PERMISSION_BLUETOOTH=1',
        'PERMISSION_APP_TRACKING_TRANSPARENCY=1',
        'PERMISSION_CRITICAL_ALERTS=1',
        'PERMISSION_ASSISTANT=1',
      ]
    end
  end
end

移除你想要使用的权限前面的#字符。例如,如果你需要访问日历,请确保代码看起来像这样:

## dart: PermissionGroup.calendar
'PERMISSION_EVENTS=1',

删除相应的权限描述在Info.plist文件中。例如,如果你不需要相机权限,请删除NSCameraUsageDescription

如何使用

有许多权限

你可以获取一个权限的状态,该状态可以是granted(已授予)、denied(已拒绝)、restricted(受限)、permanentlyDenied(永久拒绝)、limited(有限)或provisional(临时)。

var status = await Permission.camera.status;
if (status.isDenied) {
  // 我们还没有请求过权限或者权限被拒绝但不是永久拒绝。
}

// 你也可以直接请求权限的状态。
if (await Permission.location.isRestricted) {
  // 操作系统限制了访问,例如因为家长控制。
}

你也可以使用这种样式,使代码更具可读性。

await Permission.camera
  .onDeniedCallback(() {
    // 你的代码
  })
  .onGrantedCallback(() {
    // 你的代码
  })
  .onPermanentlyDeniedCallback(() {
    // 你的代码
  })
  .onRestrictedCallback(() {
    // 你的代码
  })
  .onLimitedCallback(() {
    // 你的代码
  })
  .onProvisionalCallback(() {
    // 你的代码
  })
  .request();

调用request()方法来请求一个权限。如果它已经被授予过,则什么都不会发生。request()方法返回权限的新状态。

if (await Permission.contacts.request().isGranted) {
  // 要么权限已经授予过,要么用户刚刚授予了权限。
}

// 你可以一次请求多个权限。
Map<Permission, PermissionStatus> statuses = await [
  Permission.location,
  Permission.storage,
].request();
print(statuses[Permission.location]);

一些权限,例如位置或加速度传感器权限,有一个关联的服务,该服务可以是enabled(启用)或disabled(禁用)。

if (await Permission.locationWhenInUse.serviceStatus.isEnabled) {
  // 使用位置。
}

你也可以打开应用设置:

if (await Permission.speech.isPermanentlyDenied) {
  // 用户选择永远不再看到此应用的权限请求对话框。现在唯一的办法是让用户手动在系统设置中启用它。
  openAppSettings();
}

在Android上,你可以显示一个理由来请求权限:

bool isShown = await Permission.contacts.shouldShowRequestRationale;

一些权限将不会显示一个询问用户是否允许或拒绝请求权限的对话框。这是因为正在检索应用的OS设置。设置的状态将决定权限是granted(已授予)还是denied(已拒绝)。

以下权限将不会显示对话框:

  • Notification(通知)
  • Bluetooth(蓝牙)

以下权限将不会显示对话框,但会打开相应的设置意图,以便用户更改权限状态:

  • manageExternalStorage(管理外部存储)
  • systemAlertWindow(系统警报窗口)
  • requestInstallPackages(请求安装包)
  • accessNotificationPolicy(访问通知策略)

locationAlways权限不能直接请求,用户必须首先请求locationWhenInUse权限。接受此权限(点击“仅在使用应用时允许”)将给用户提供一个请求locationAlways权限的机会。这将弹出另一个权限弹窗,询问你是选择“仅在使用时保持”还是“始终允许”。

常见问题

请求“storage”权限在Android 13+上总是返回“denied”。我该怎么办?

在Android上,Permission.storage权限与Android的READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE权限相关联。从Android 10(API 29)开始,READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE权限已被标记为弃用,并在Android 13(API 33)中完全移除/禁用。

如果你的应用需要访问媒体文件,Google建议使用READ_MEDIA_IMAGESREAD_MEDIA_VIDEOREAD_MEDIA_AUDIO权限。这些可以通过Permission.photosPermission.videosPermission.audio分别请求。要请求这些权限,请确保android/app/build.gradle文件中的compileSdkVersion设置为33。

如果你的应用需要访问Android的文件系统,可以请求MANAGE_EXTERNAL_STORAGE权限(使用Permission.manageExternalStorage)。从Android 11(API 30)开始,MANAGE_EXTERNAL_STORAGE权限被视为高风险或敏感权限。因此,如果你打算通过Google Play商店发布应用,需要声明使用这些权限

请求Permission.locationAlways在Android 10+(API 29+)上总是返回“denied”。我该怎么办?

从Android 10开始,应用在请求读取设备位置之前,需要先获得读取设备位置的前台权限。直接请求“始终允许”位置权限或同时请求这两个权限时,系统会忽略请求。因此,不要只调用Permission.location.request(),而是先调用Permission.location.request()Permission.locationWhenInUse.request(),以获得读取GPS的权限。一旦获得了这个权限,你可以调用Permission.locationAlways.request()。这将向用户提供更新设置的选项,以便在后台始终读取位置。更多详细信息,请参阅Android文档

检查或请求权限终止了iOS应用。我该怎么办?

首先确保ios/Runner/Info.plist文件包含应用所需的所有权限条目。如果缺少条目,iOS会在检查或请求特定权限时终止应用。

如果应用需要访问SiriKit(通过请求Permission.assistant权限),还请确保将com.apple.developer.siri权限添加到应用配置中。为此,在项目的ios/Runner文件夹中创建一个名为Runner.entitlements的文件(如果尚未存在)。打开文件并添加以下内容:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.developer.siri</key>
	<true/>
</dict>
</plist>

重要部分是带有值com.apple.developer.sirikey元素和<true/>元素。根据应用的需求,这个文件也可能包含其他权限。

示例代码

以下是一个完整的示例代码,展示了如何使用in_app_permission插件请求相机权限。

import 'package:flutter/material.dart';
import 'package:in_app_permission/in_app_permission.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  void _permission() {
    InAppPermission.checkAndRequest(Permission.camera).then((value) {
      if (value.isGranted) {
        // Do something
      }
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('You have pushed the button this many times:'),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _permission,
        tooltip: 'permission',
        child: const Icon(Icons.add),
      ),
    );
  }
}

更多关于Flutter应用内权限请求插件in_app_permission的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter应用内权限请求插件in_app_permission的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter应用中使用in_app_permission插件进行权限请求的示例代码。这个插件允许你在应用内请求各种权限,并处理用户的响应。

首先,确保你的Flutter项目中已经添加了in_app_permission插件。你可以在你的pubspec.yaml文件中添加以下依赖项:

dependencies:
  flutter:
    sdk: flutter
  in_app_review: ^3.0.0  # 请检查最新版本号

注意:in_app_permission插件的实际名称可能是permission_handler或者其他类似的名称,因为in_app_permission可能不是一个真实存在的Flutter插件。这里我将使用permission_handler作为示例,因为它是Flutter社区中广泛使用的权限请求插件。

接下来,运行flutter pub get来安装依赖项。

安装完成后,你可以在你的Flutter应用中按照以下步骤请求权限:

  1. 导入必要的包
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
  1. 请求权限并处理响应
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Permission Handler Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: PermissionRequestScreen(),
    );
  }
}

class PermissionRequestScreen extends StatefulWidget {
  @override
  _PermissionRequestScreenState createState() => _PermissionRequestScreenState();
}

class _PermissionRequestScreenState extends State<PermissionRequestScreen> {
  bool _locationPermissionGranted = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Permission Request Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Location Permission Granted: $_locationPermissionGranted',
              style: TextStyle(fontSize: 20),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _requestLocationPermission,
              child: Text('Request Location Permission'),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _requestLocationPermission() async {
    // 请求位置权限
    var status = await Permission.location.status;

    if (!status.isGranted) {
      var result = await Permission.location.request();
      if (result.isGranted) {
        setState(() {
          _locationPermissionGranted = true;
        });
      } else if (result.isDenied || result.isPermanentlyDenied) {
        // 用户拒绝权限请求
        showDialog(
          context: context,
          builder: (BuildContext context) {
            return AlertDialog(
              title: Text('Permission Denied'),
              content: Text('Location permission is required for this feature to work.'),
              actions: <Widget>[
                TextButton(
                  onPressed: () {
                    Navigator.of(context).pop();
                    // 可以引导用户去设置中手动开启权限
                    // openAppSettings();
                  },
                  child: Text('OK'),
                ),
              ],
            );
          },
        );
      }
    } else {
      setState(() {
        _locationPermissionGranted = true;
      });
    }
  }
}

在上面的代码中,我们定义了一个简单的Flutter应用,它包含一个按钮,用于请求位置权限。如果用户授予权限,我们将更新UI以反映权限已被授予。如果用户拒绝权限请求,我们将显示一个对话框,提示用户权限被拒绝。

注意:在实际应用中,你可能还需要处理Permission.locationWhenInUsePermission.locationAlways,具体取决于你的应用需求。此外,当用户拒绝权限并选择了“不再询问”(即isPermanentlyDenied状态)时,你可能需要引导用户去系统设置中手动开启权限,这可以通过调用openAppSettings()方法来实现。不过,请注意openAppSettings()方法在某些平台上可能不可用或需要额外的权限声明。

回到顶部