Flutter iOS CallKit集成插件ios_callkit的使用
Flutter iOS CallKit集成插件ios_callkit的使用
动机
我们使用CallKit来处理iOS 13以上的VoIP通知。查看WWDC2019视频了解更多信息。因此,我们需要将CallKit和PushKit一起使用。尽管网络上有一些VoIP通知的示例,但专门针对Flutter的很少。我决定创建一个flutter插件,包含所需的最小功能。你可以使用这个插件,但实际目的是帮助你根据自己的服务创建一个定制的VoIPKit。
需求
- 仅支持iOS,不支持Android。
- 需要iOS 10或以上版本。
- 仅支持一对一通话,不支持群组通话。
- 需要一个服务器来推送VoIP通知(通过APNs)。
- 要实际进行视频或语音通话,你需要连接到一个服务,例如WebRTC(例如:Agora, SkyWay, Amazon Kinesis Video Streams)。
使用方法
1. 安装
在你的pubspec.yaml
文件中添加ios_callkit
作为依赖项。
dependencies:
ios_callkit: ^x.x.x
2. 在Xcode中设置Capability
- 选择背景模式 > 语音过IP和远程通知为开启。
- 选择推送通知。
- 在选择能力后修改
ios/Runner/Info.plist
文件。
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
<string>voip</string>
</array>
3. 编辑Info.plist
编辑ios/Runner/Info.plist
文件如下:
<key>FIVKIconName</key>
<string>AppIconName</string>
<key>FIVKLocalizedName</key>
<string>ios_callkit</string>
<key>FIVKSkipRecallScreen</key>
<true/>
4. 为CallKit添加新的图像集
在ios/Runner/Assets.xcassets/AppIcon-VoIPKit
目录下添加一个图标(.png
或 .pdf
),用于锁屏时来电显示。
5. 创建VoIP服务证书
- 访问Apple开发者网站并创建一个新的VoIP服务证书(
.cer
)。更多信息请查看此文档。 - 使用KeyChainAccess从
.cer
生成.p12
文件,并使用openssl生成.pem
文件。
6. 从你的服务器请求VoIP通知APNs
查看Apple文档以了解更多信息。
示例数据(payload)如下:
{
"aps": {
"alert": {
"uuid": "<Version 4 UUID (e.g.: https://www.uuidgenerator.net/version4)>",
"incoming_caller_id": "<your service user id>",
"incoming_caller_name": "<your service user name>",
"videoType": true
}
}
}
可以使用curl测试VoIP通知,如下所示:
curl -v \
-d '{"aps":{"alert":{"uuid":"982cf533-7b1b-4cf6-a6e0-004aab68c503","incoming_caller_id":"0123456789","incoming_caller_name":"Tester","videoType":true}}}' \
-H "apns-push-type: voip" \
-H "apns-expiration: 0" \
-H "apns-priority: 0" \
-H "apns-topic: <your app’s bundle ID>.voip" \
--http2 \
--cert ./voip_services.pem \
https://api.sandbox.push.apple.com/3/device/<VoIP device Token for your iPhone>
示例代码
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:ios_callkit/ios_callkit.dart';
import 'incoming_call_page.dart';
import 'outgoing_call_page.dart';
enum ExampleAction { RequestAuthorization, GetSettings }
extension on ExampleAction {
String get title {
switch (this) {
case ExampleAction.RequestAuthorization:
return 'Authorize Notifications';
case ExampleAction.GetSettings:
return 'Check Settings';
default:
return 'Unknown';
}
}
}
void main() {
WidgetsFlutterBinding.ensureInitialized();
runZonedGuarded(() {
IOSCallKit.instance.onDidUpdatePushToken = (token) {
print('🎈 example: onDidUpdatePushToken token = $token');
};
runApp(MaterialApp(
routes: <String, WidgetBuilder>{
OutgoingCallPage.routeName: (_) => OutgoingCallPage(),
IncomingCallPage.routeName: (_) => IncomingCallPage(),
},
home: SelectCallRoll(),
));
}, (object, stackTrace) {});
}
class SelectCallRoll extends StatefulWidget {
[@override](/user/override)
_SelectCallRollState createState() => _SelectCallRollState();
}
class _SelectCallRollState extends State<SelectCallRoll> {
void _performExampleAction(ExampleAction action) async {
switch (action) {
case ExampleAction.RequestAuthorization:
final granted = await IOSCallKit.instance.requestAuthLocalNotification();
print('🎈 example: requestAuthLocalNotification granted = $granted');
break;
case ExampleAction.GetSettings:
final settings = await IOSCallKit.instance.getLocalNotificationsSettings();
print(
'🎈 example: getLocalNotificationsSettings settings: \n$settings');
showDialog(
context: context,
builder: (ctx) {
return AlertDialog(
title: Text('Settings'),
content: Text('$settings'),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: Text('Ok'),
)
],
);
});
break;
default:
break;
}
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Select call roll'),
actions: [
PopupMenuButton<ExampleAction>(
icon: Icon(Icons.more_vert),
onSelected: (action) => _performExampleAction(action),
itemBuilder: (BuildContext context) {
return ExampleAction.values.map((ExampleAction choice) {
return PopupMenuItem<ExampleAction>(
value: choice,
child: Text(choice.title),
);
}).toList();
},
),
],
),
body: SafeArea(
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const Text(
'📱 To try out the example app, you need two iPhones with iOS 10 or later.',
textAlign: TextAlign.center,
style: TextStyle(
height: 1.5,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
_button(isCaller: true),
_button(isCaller: false),
],
),
),
),
),
);
}
Widget _button({
required bool isCaller,
}) {
return SizedBox(
width: 140,
height: 140,
child: RawMaterialButton(
padding: EdgeInsets.zero,
elevation: 8.0,
shape: CircleBorder(),
fillColor: Colors.blue,
onPressed: () {
Navigator.pushNamed(
context,
isCaller ? OutgoingCallPage.routeName : IncomingCallPage.routeName,
);
},
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Icon(
isCaller ? Icons.call : Icons.ring_volume,
size: 32,
),
Text(
isCaller ? '🤙 Caller' : '🔔 Callee',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
color: Colors.white,
),
),
],
),
),
);
}
}
更多关于Flutter iOS CallKit集成插件ios_callkit的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter iOS CallKit集成插件ios_callkit的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
在Flutter项目中集成iOS的CallKit功能,可以使用ios_callkit
插件。下面是一个示例代码,展示了如何在Flutter应用中集成并使用ios_callkit
插件来实现基本的CallKit功能。
1. 添加依赖
首先,在你的pubspec.yaml
文件中添加ios_callkit
依赖:
dependencies:
flutter:
sdk: flutter
ios_callkit: ^x.y.z # 请替换为最新版本号
然后运行flutter pub get
来安装依赖。
2. 配置iOS项目
由于ios_callkit
是iOS特定的插件,你需要在Xcode中进行一些配置。
- 打开Xcode,选择你的Runner项目。
- 在
Info.plist
中添加必要的权限配置,比如NSMicrophoneUsageDescription
和NSCameraUsageDescription
(如果你的应用需要这些权限)。 - 确保你的应用具有VoIP后台模式权限。在
Capabilities
标签页中,启用Background Modes
并勾选Voice over IP
。
3. 编写Flutter代码
下面是一个简单的Flutter应用示例,展示如何使用ios_callkit
插件:
import 'package:flutter/material.dart';
import 'package:ios_callkit/ios_callkit.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
CXProvider? provider;
@override
void initState() {
super.initState();
_initCallKit();
}
@override
void dispose() {
provider?.reportEndCall(withUUID: 'your-call-uuid', reason: CXEndCallReasonEnded);
provider = null;
super.dispose();
}
Future<void> _initCallKit() async {
if (Platform.isIOS) {
provider = await CXProvider.init(
configuration: CXProviderConfiguration(
localizedName: 'Your App Name',
supportsVideo: false,
maximumCallsPerCallGroup: 1,
supportsReinviting: false,
supportsHolding: false,
supportsGrouping: false,
supportsUngrouping: false,
),
);
provider?.setDelegate(CXProviderDelegate(
provider: provider!,
performAnswerCallAction: (CXAnswerCallAction action) async {
// Handle answer call action
action.fulfill();
},
performEndCallAction: (CXEndCallAction action) async {
// Handle end call action
action.fulfill();
},
providerDidReset: () {
// Handle provider reset
},
));
}
}
Future<void> _startCall() async {
if (provider != null) {
var uuid = UUID().v4(); // Generate a unique UUID for the call
var handle = CXHandle(type: CXHandleType.generic, value: 'caller-id');
var update = CXCallUpdate();
update.remoteHandle = handle;
update.hasVideo = false;
provider!.reportNewIncomingCall(
withUUID: uuid,
update: update,
completion: (CXError? error) {
if (error == null) {
// Call successfully reported
} else {
// Handle error
}
},
);
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('CallKit Demo'),
),
body: Center(
child: ElevatedButton(
onPressed: _startCall,
child: Text('Start Call'),
),
),
),
);
}
}
4. UUID生成
在上面的代码中,我们使用了UUID().v4()
来生成一个唯一的UUID。你需要确保在你的项目中引入了uuid
包。在pubspec.yaml
中添加依赖:
dependencies:
uuid: ^x.y.z # 请替换为最新版本号
然后运行flutter pub get
。
5. 运行应用
现在你可以运行你的Flutter应用,点击“Start Call”按钮来模拟一个来电,并观察CallKit的集成效果。
请注意,这只是一个简单的示例,实际应用中你可能需要处理更多的逻辑,比如来电界面的自定义、来电铃声的设置、VoIP推送的配置等。详细的信息可以参考ios_callkit
插件的官方文档和Apple的CallKit文档。