深色模式
Flutter处理App动态权限(移动端)
动态权限处理流程
大致流程:先检查权限,再请求权限。获得授权,就使用,没有获得授权,就提示用户。
检查权限有3种结果
- 永久拒绝:弹窗提示用户,打开系统设置,手动开启权限。因为此时无法在App中发起权限请求。
- 拒绝:先弹窗提示用户,该权限的用途,然后发起权限请求。
- 授权:直接使用。
请求权限有3种结果
- 永久拒绝:弹窗提示用户,打开系统设置,手动开启权限。
- 拒绝:弹窗提示用户,没有获得授权,该功能无法使用。
- 授权:直接使用。
第三方库
permission_handler:检查权限、请求权限
(在iOS端,不仅要修改Info.plist
,还要修改Podfile
。)
app_settings:跳转到系统设置,手动开关权限
实现
按照以上处理流程,封装一个class
。
(由于目前的合规要求,在App中一次性申请多个权限的情况比较少,所以此封装仅考虑处理单个权限。)
dart
import 'package:app_settings/app_settings.dart';
import 'package:flutter/cupertino.dart';
import 'package:permission_handler/permission_handler.dart';
/// 动态权限的处理流程
/// 1. 检查权限
/// 2. 提示请求权限的用途
/// 3. 请求权限
class PermissionX {
PermissionX({
required this.permission,
this.title = "提示",
this.message = "<申请此权限的原因>",
this.titleForDenied = "提示",
this.messageForDenied = "<用户拒绝授权,功能无法使用>",
this.titleForDeniedPermanently = "提示",
this.messageForDeniedPermanently = "<用户永久拒绝,提示到系统设置中打开权限>",
});
final Permission permission;
final String title;
final String message;
final String titleForDenied;
final String messageForDenied;
final String titleForDeniedPermanently;
final String messageForDeniedPermanently;
Future<bool> ensurePermission(BuildContext context) async {
/* 请求 */
Future<bool> doRequest() async {
var available = false;
var ok = await _tipReason(context);
if (!ok) {
return false;
}
var reqResult = await _requestPermission();
if (!context.mounted) return false;
switch (reqResult) {
case -1:
_tipOnPermanentlyDenied(context);
case 0:
_tipOnDenied(context);
case 1:
available = true;
}
return available;
}
/* 检查 */
var available = false;
var checkResult = await _checkPermission();
if (!context.mounted) return false;
switch (checkResult) {
case -1:
_tipOnPermanentlyDenied(context);
case 0:
available = await doRequest();
case 1:
available = true;
}
return available;
}
/// 检查权限的状态
Future<int> _checkPermission() async {
var status = await permission.status;
// log("[检查权限] ${permission.toString()}: ${status.name}");
switch (status) {
// 【仅iOS】用户想开也开不了(比如家长控制)
case PermissionStatus.restricted:
return -1;
// 【仅iOS】与通知有关,不懂
case PermissionStatus.provisional:
return -1;
// 【仅iOS】授予有限的使用权
case PermissionStatus.limited:
return -1;
// 【Android、iOS】永久拒绝
// 对于Android:
// - 版本30开始,连续拒绝2次,即永久拒绝,
// - 版本30之前,主动选择永久拒绝
// 对于iOS:
// - 代表拒绝过1次
case PermissionStatus.permanentlyDenied:
return -1;
// 【Android、iOS】本次拒绝
case PermissionStatus.denied:
return 0;
// 【Android、iOS】获得授权
case PermissionStatus.granted:
return 1;
}
}
/// 请求一次权限
Future<int> _requestPermission() async {
var status = await permission.request();
// log("[申请权限] ${permission.toString()}: ${status.name}");
switch (status) {
// 【仅iOS】用户想开也开不了(比如家长控制)
case PermissionStatus.restricted:
return -1;
// 【仅iOS】与通知有关,不懂
case PermissionStatus.provisional:
return -1;
// 【仅iOS】授予有限的使用权
case PermissionStatus.limited:
return -1;
// 【Android、iOS】永久拒绝
// 对于Android:
// - 版本30开始,连续拒绝2次,即永久拒绝,
// - 版本30之前,主动选择永久拒绝
// 对于iOS:
// - 代表拒绝过1次
case PermissionStatus.permanentlyDenied:
return -1;
// 【Android、iOS】本次拒绝
case PermissionStatus.denied:
return 0;
// 【Android、iOS】获得授权
case PermissionStatus.granted:
return 1;
}
}
/// 请求之前,弹窗提示
Future<bool> _tipReason(BuildContext context) async {
return await showCupertinoDialog<bool>(
context: context,
builder: (context) => CupertinoAlertDialog(
title: Text(title),
content: Text(message),
actions: [
CupertinoDialogAction(
onPressed: () => Navigator.of(context).pop(true),
isDefaultAction: true,
child: const Text('继续'),
),
],
),
) ??
false;
}
/// 被拒,弹窗提示
_tipOnDenied(BuildContext context) {
showCupertinoDialog(
context: context,
builder: (context) => CupertinoAlertDialog(
title: Text(titleForDenied),
content: Text(messageForDenied),
actions: [
// CupertinoDialogAction(
// onPressed: () => Navigator.of(context).pop(false),
// isDefaultAction: true,
// child: const Text('取消'),
// ),
CupertinoDialogAction(
onPressed: () => Navigator.of(context).pop(true),
isDestructiveAction: true,
child: const Text('知道了'),
),
],
),
);
}
/// 被永拒,弹窗提示去系统设置
_tipOnPermanentlyDenied(BuildContext context) {
showCupertinoDialog(
context: context,
builder: (context) => CupertinoAlertDialog(
title: Text(titleForDeniedPermanently),
content: Text(messageForDeniedPermanently),
actions: [
CupertinoDialogAction(
onPressed: () => Navigator.of(context).pop(false),
isDefaultAction: true,
child: const Text('取消'),
),
CupertinoDialogAction(
onPressed: () {
AppSettings.openAppSettings();
Navigator.of(context).pop(true);
},
isDestructiveAction: true,
child: const Text('去设置'),
),
],
),
);
}
}