tencent_cloud_chat_uikit/doc/混合开发.md

939 lines
41 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[](id:toc)
通过阅读本文,你可以了解在您现有的 Android / iOS 原生开发项目中集成腾讯云IM Flutter 的方法。
有的时候使用Flutter重写您现有的应用程序是不现实的。如果您想在现有APP中使用腾讯云IM的能力推荐采用混合开发方案即将Flutter模块嵌入您的原生开发APP项目中。
**可在很大程度上降低您的工作量快速在双端原生APP中植入IM通信能力。**
![](https://qcloudimg.tencent-cloud.cn/raw/54adc2b0587f9f30d56e96eb6461b969.png)
## 环境要求
| 环境 | 版本 |
|---------|---------|
| Flutter | SDK 最低要求 Flutter 2.2.0版本TUIKit 集成组件库最低要求 Flutter 2.10.0 版本。|
|Android|Android Studio 3.5及以上版本App 要求 Android 4.1及以上版本设备。|
|iOS|Xcode 11.0及以上版本,请确保您的项目已设置有效的开发者签名。|
|腾讯云IM SDK|[tencent_im_sdk_plugin](https://pub.dev/packages/tencent_im_sdk_plugin) 5.0 及以上版本, [tencent_cloud_chat_uikit](https://pub.dev/packages/tencent_cloud_chat_uikit) 0.2 及以上版本。|
## 快速了解
<div class="doc-video-mod"><iframe src="https://cloud.tencent.com/edu/learning/quick-play/3856-67266?source=gw.doc.media&withPoster=1&notip=1"></iframe></div>
>?
>
> 对于以上的Demo项目源代码可在我们的[GitHub仓库](https://github.com/TencentCloud/tencentchat-add-flutter-to-app)中找到,欢迎查阅。
## 前置知识点
开始之前您需要了解腾讯云IM Flutter SDK及TUIKit的用法及Flutter-原生混合开发原理。
### 腾讯云IM
#### 总体入门
在开始前您首先需要了解腾讯云IM Flutter的SDK构成及使用方式。
主要包括两个SDK[无UI版本](https://cloud.tencent.com/document/product/269/68823#.E7.AC.AC.E4.BA.94.E9.83.A8.E5.88.86.EF.BC.9A.E8.87.AA.E5.AE.9E.E7.8E.B0-ui-.E9.9B.86.E6.88.90)及[含UI组件库](https://cloud.tencent.com/document/product/269/70747)。本文将以 [含UI组件库TUIKit](https://cloud.tencent.com/document/product/269/70747) 为例,介绍混合开发方案。
**关于腾讯云IM Flutter详细用法可从我们的 [快速入门文档](https://cloud.tencent.com/document/product/269/68823) 看起。**
[](id:modules)
#### 两个模块
腾讯云IM主要有两个部分包括 Chat聊天模块 和 Call通话模块。
Chat聊天模块主要包括消息收发、会话管理、用户关系管理等。
Call通话模块主要包括音视频通话包括一对一通话和群组多人通话。
### Flutter 混合开发
核心原理是,将 module 形式的Flutter项目打包成Native端的可执行程序嵌入Native项目中。因Flutter module可以通用因此仅需编写一次Flutter module即可嵌入 Android/iOS APP 中。
当您现有应用需要展示腾讯云IM相关页面时可加载对应用于承载Flutter的ActivityAndroid或ViewControlleriOS
当需要两端通信时,如传递当前用户信息,传递音视频通话数据,触发离线推送数据,可采用[Method Channel](https://docs.flutter.dev/development/platform-integration/platform-channels#channels-and-platform-threading)方式进行。触发另一端的方法使用 `invokeMethod`,监听另一端发来的方法调用使用[预挂载的Method Channel监听器](https://docs.flutter.dev/development/platform-integration/platform-channels#executing-channel-handlers-on-background-threads)。
[](id:android)
#### 将 Flutter 模块添加至 Android 项目中
[详细学习](https://docs.flutter.dev/development/add-to-app/android/project-setup)
将Flutter module添加为Gradle中现有应用程序的依赖项。有两种方式可以实现这一点。
##### Android方式一依赖 Android Archive (AAR)
AAR机制创建通用的Android AAR作为打包Flutter module的中介。如果您经常构建它会增加一个构建步骤。
该选项将Flutter库打包为由AAR和POMS构件组成的通用本地Maven存储库。此选项允许您的团队在不安装Flutter SDK的情况下构建主机应用程序。然后您可以从本地或远程存储库中分发构件。
因此,建议在线上生产环境,使用本方案。
**具体步骤:**
在您的Flutter module中运行
```shell
flutter build aar
```
然后,按照屏幕上的说明进行集成。
![](https://qcloudimg.tencent-cloud.cn/raw/32e9376de02da10e97a8c54b9ab2b51c.png)
您的应用程序现在将Flutter模块作为依赖项包括在内。
##### Android方式二依赖Flutter module源代码
源代码子项目机制是一个方便的一键构建过程但需要Flutter SDK。这是Android Studio IDE插件使用的机制。
此方式可为您的Android项目和Flutter项目实现一步构建。当您同时处理两个部分并快速迭代时此选项很方便但您的团队必须安装Flutter SDK才能构建应用程序。
因此,建议在开发测试环境,使用本方案。
**具体步骤:**
将Flutter module作为一个子项目添加至宿主APP的 `settings.gradle` 中:
```gradle
// Include the host app project.
include ':app' // assumed existing content
setBinding(new Binding([gradle: this])) // new
evaluate(new File( // new
settingsDir.parentFile, // new
'tencent_chat_module/.android/include_flutter.groovy' // new
)) // new
```
在您应用中的 `app/build.gradle => dependencies` 中引入对Flutter module的 `implementation`:
```gradle
dependencies {
implementation project(':flutter')
}
```
您的应用程序现在将Flutter模块作为依赖项包括在内。
[](id:ios)
#### 将 Flutter 模块添加至 iOS 项目中
[详细学习](https://docs.flutter.dev/development/add-to-app/ios/project-setup#embed-the-flutter-module-in-your-existing-application)
有两种方法可以在现有应用程序中嵌入Flutter。
##### iOS方式一嵌入 CocoaPods 和 Flutter SDK 集成
使用CocoaPods依赖项管理器并安装Flutter SDK。这种方法要求每个从事项目工作的开发人员都有一个本地安装的Flutter SDK版本。
只需在Xcode中构建您的应用程序即可自动运行脚本来嵌入您的DART和插件代码。这允许快速迭代最新版本的颤振模块而无需在Xcode之外运行其他命令。
因此,建议在开发测试环境,使用本方案。
**具体步骤:**
将以下代码添加到Podfile中:
```
// 上一步构建的Flutter Module的路径
flutter_chat_application_path = '../tencent_chat_module'
load File.join(flutter_chat_application_path, '.ios', 'Flutter', 'podhelper.rb')
```
对于每个需要嵌入Flutter的[Podfile target](https://guides.cocoapods.org/syntax/podfile.html#target),调用 `install_all_flutter_pods(flutter_chat_application_path)`.
```
target 'MyApp' do
install_all_flutter_pods(flutter_chat_application_path)
end
```
在Podfile的 `post_install` 块中,调用 `flutter_post_install(installer)`,并完成 [腾讯云IM TUIKit](https://cloud.tencent.com/document/product/269/70747) 所需的权限声明,包括麦克风权限/相机权限/相册权限。
```
post_install do |installer|
flutter_post_install(installer) if defined?(flutter_post_install)
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_MICROPHONE=1',
'PERMISSION_CAMERA=1',
'PERMISSION_PHOTOS=1',
]
end
end
end
```
执行 `pod install`
> ?
>
> - 在 `tencent_chat_module/pubspec.yaml` 中更改Flutter插件依赖时请在Flutter Module目录中运行 `flutter pub get` 以刷新 `podhelper.rb` 脚本读取的插件列表。然后从您iOS应用程序的根目录再次执行 `pod install`。
> - 对于 Apple Silicon 芯片 arm64 架构的 Mac电脑可能需要执行 `arch -x86_64 pod install --repo-update`。
`podhelper.rb` 脚本将您的插件 / `Flutter.framework` / `App.framework` 植入您的项目中。
##### iOS方式二在Xcode中嵌入frameworks
为Flutter引擎、已编译的DART代码和所有Flutter插件创建框架。手动嵌入框架并在Xcode中更新现有应用程序的构建设置。
通过手动编辑现有的Xcode项目您可以生成必要的framework并将它们嵌入到应用程序中。如果您的团队成员无法在本地安装Flutter SDK和CocoaPods或者如果您不想在现有应用程序中使用CocoaPods作为依赖项管理器则可以这样做。每次你在你的颤动模块中修改代码时你都必须运行 `flutter build ios-framework`.
因此,建议在线上环境,使用本方案。
**具体步骤:**
在您的Flutter module中运行如下代码。
下面的示例假设您想要将framework生成到 `some/path/MyApp/Flutter/`.
```shell
flutter build ios-framework --output=some/path/MyApp/Flutter/
```
在 Xcode 中将生成的 frameworks 集成到你的既有应用中。例如,你可以在 `some/path/MyApp/Flutter/Release/` 目录拖拽 frameworks 到你的应用 target 编译设置的 General > Frameworks, Libraries, and Embedded Content 下,然后在 Embed 下拉列表中选择 “Embed & Sign”。
## 混合开发选型
我们推荐您使用Flutter Module方式进行混合开发集成。
在Native原生项目中构建Flutter引擎来承载Flutter中的Chat及Call模块。有关两个模块的介绍[请看此处](#modules)。
对于Flutter引擎的创建管理目前两种方式单Flutter引擎及多Flutter引擎。
| 引擎模式 | 介绍 | 优点 | 缺点 | Demo源码下载 |
|---------|---------|---------|---------|---------|
| [Flutter单引擎](#single) | Chat模块和Call模块在同一个Flutter引擎中承载。 | 方便所有Flutter代码统一维护。 | 由于Call插件在有电话呼入时需要自动展示来电页面。如果在同一个引擎中需要强制跳转至Flutter所在页面体验较差。 | [点击下载](https://github.com/TencentCloud/tencentchat-add-flutter-to-app/tree/main/Single%20Flutter%20Engines) |
| [Flutter多引擎](#multiple) | Chat模块和Call模块分别承载于不同的Flutter引擎中使用Flutter引擎组来统一管理这两个引擎。 | Call插件独立存在于一个Flutter引擎中独立页面控制来电时直接将该页面弹窗即可不影响用户当前所在页面体验较好。 | 通话模块无法最小化成浮窗形式。 | [点击下载](https://github.com/TencentCloud/tencentchat-add-flutter-to-app/tree/main/Multiple%20Flutter%20Engines) |
此外,我们还提供,将腾讯云 IM Native SDK 与 Flutter SDK 结合使用的方案,[适用场景和步骤介绍可查看这里](#native)。[Demo源码下载](https://github.com/TencentCloud/tencentchat-add-flutter-to-app/tree/main/Initialize%20from%20Native)。
[](id:multiple)
## 方案一Flutter 多引擎方案【推荐】
本方案中Chat 和 Call 模块分别独立于不同的Flutter引擎。
使用多个Flutter引擎的优点是每个实例都是独立的并维护其自己的内部导航堆栈、UI和应用程序状态。这简化了整个应用程序代码的状态保持责任并提高了模块化能力。
![](https://qcloudimg.tencent-cloud.cn/raw/912d986a5ff57606422455a273a033f3.png)
在Android和iOS上添加多个Flutter引擎主要基于一个FlutterEngineGroup类(Android API、iOS API)来构造并管理多个FlutterEngineFlutter引擎
在我们的项目中我们基于一个统一的FlutterEngineGroup来管理两个FlutterEngineFlutter引擎分别用于承载 Chat 和 Calling 模块。
[![](https://qcloudimg.tencent-cloud.cn/raw/b622951f776a505e83f843de1f62fffc.png)](https://github.com/TencentCloud/tencentchat-add-flutter-to-app/tree/main/Multiple%20Flutter%20Engines)
### Flutter Module 开发
要将Flutter嵌入到现有应用程序中请首先创建一个Flutter模块。
在您项目的根目录外层,运行
```
cd some/path/
flutter create --template module tencent_chat_module
```
这会在 some/path/tencent_chat_module/ 创建一个 Flutter 模块项目。 在该目录中,您可以运行与在任何其他 Flutter 项目中相同的 Flutter 命令,例如 `flutter run --debug``flutter build ios`。 您还可以使用 Flutter 和 Dart 插件在 Android Studio, IntelliJ 或 VS Code 中运行该模块。 该项目在嵌入到现有应用程序之前包含模块的单视图示例版本,这对于测试代码的仅 Flutter 部分很有用。
`tencent_chat_module` 模块目录结构类似于普通的 Flutter 应用程序:
```
tencent_chat_module/
├── .ios/
│ ├── Runner.xcworkspace
│ └── Flutter/podhelper.rb
├── lib/
│ └── main.dart
├── test/
└── pubspec.yaml
```
现在,我们可以在 `lib/` 中,编写代码了。
#### 梳理Flutter lib 目录
>?
>
> 以下代码结构仅供参考您可根据需要灵活组织以引入腾讯云IM Flutter。
`lib/` 我们创建三个目录,`call`, `chat`, `common`。分别用于放置通话引擎IM引擎及通用model类。
```
tencent_chat_module/
├── lib/
│ └── call/
│ └── chat/
│ └── common/
```
#### 通用model类模块
新建 `common/common_model.dart` 文件如下所示新建两个class用于定义Flutter与原生应用通信规范。
```dart
class ChatInfo {
String? sdkappid;
String? userSig;
String? userID;
ChatInfo.fromJSON(Map<String, dynamic> json) {
sdkappid = json["sdkappid"].toString();
userSig = json["userSig"].toString();
userID = json["userID"].toString();
}
Map<String, String> toMap(){
final Map<String, String> map = {};
if(sdkappid != null){
map["sdkappid"] = sdkappid!;
}
if(userSig != null){
map["userSig"] = userSig!;
}
if(userID != null){
map["userID"] = userID!;
}
return map;
}
}
class CallInfo{
String? userID;
String? groupID;
CallInfo();
CallInfo.fromJSON(Map<String, dynamic> json) {
groupID = json["groupID"].toString();
userID = json["userID"].toString();
}
Map<String, String> toMap(){
final Map<String, String> map = {};
if(userID != null){
map["userID"] = userID!;
}
if(groupID != null){
map["groupID"] = groupID!;
}
return map;
}
}
```
#### Chat 模块
**首先编写IM引擎。本模块所有代码及文件均在 `lib/chat` 目录下。**
1. 新建全局状态管理Model名为 `model.dart`
该Model用于挂载初始化并管理腾讯云IM Flutter模块离线推送能力全局状态管理维护与Native间通信。
是整个Chat模块的核心。
详细代码可查看Demo源码。重点关注三个部分
- Future<dynamic> _handleMessage(MethodCall call): 动态监听 Native 透传来的事件,包括登录信息及点击推送事件。
- Future<void> handleClickNotification(Map<String, dynamic> msg): 点击通知处理事件来自Native透传从 Map 中取出数据,跳转至对应的子模块,如某个具体会话。
- Future<void> initChat(): 初始化腾讯云IM/登录腾讯云IM/并完成离线推送的初始化及Token上报。该方法使用线程锁机制保证同时只能执行一个并在初始化成功后不重复执行。
> ?
>
> 请根据 [离线推送接入指引](https://cloud.tencent.com/document/product/269/74605)完成厂商离线推送功能接入才可正常上报推送Token使用推送功能。
2. 新建 `chat_main.dart` 文件用于Chat模块主入口。
- 该页面也是Flutter Chat模块的首页。
- 在Demo中该页面在未登录前为加载状态登录后展示会话列表。
- 此外,还需要在这里,完成 `didChangeAppLifecycleState` 监听与前后台切换事件上报,详情请查看[离线推送插件文档步骤5](https://cloud.tencent.com/document/product/269/74605#.E6.AD.A5.E9.AA.A45.EF.BC.9A.E5.89.8D.E5.90.8E.E5.8F.B0.E5.88.87.E6.8D.A2.E7.9B.91.E5.90.AC.3Ca-id.3D.22step_5.22.3E.3C.2Fa.3E)。
- 详细代码可查看Demo源码。
3. 新建 `push.dart` 文件,用于单例管理 [离线推送插件](https://cloud.tencent.com/document/product/269/74605) 能力。用于获取并上报Token/获取推送权限等操作。详细代码可查看Demo源码。
4. 新建 `conversation.dart` 文件用于承载TUIKit的会话模块组件 `TIMUIKitConversation`。详细代码可查看Demo源码。
5. 新建 `chat.dart` 文件用于承载TUIKit的历史消息列表和发送消息模块组件 `TIMUIKitChat`
该页面还有跳转至 Profile 及 Group Profile 页面的能力。
详细代码可查看Demo源码。
6. 新建 `user_profile.dart` 文件用于承载TUIKit的用户信息及关系链管理模块组件 `TIMUIKitProfile`。详细代码可查看Demo源码。
7. 新建 `group_profile.dart` 文件用于承载TUIKit的群信息及群管理模块组件 `TIMUIKitGroupProfile`。详细代码可查看Demo源码。
此时Chat模块已开发完成。最终结构如下
```
tencent_chat_module/
├── lib/
│ └── call/
│ └── chat.dart
│ └── model.dart
│ └── chat_main.dart
│ └── push.dart
│ └── conversation.dart
│ └── user_profile.dart
│ └── group_profile.dart
│ └── chat/
│ └── common/
```
#### Call 模块
该模块用于承载音视频通话能力,该能力由 [音视频通话插件](https://cloud.tencent.com/document/product/269/72485) 提供。
该模块的核心是监听收到新的通话邀请时通过调用Native方法自动弹出通话页面并接受 Chat 模块经由Native转发来的通话请求主动发起通话。
**首先编写IM引擎。本模块所有代码及文件均在 `lib/call` 目录下。**
1. 新建全局状态管理Model名为 `model.dart`
该Model用于挂载初始化并管理 [音视频通话插件](https://cloud.tencent.com/document/product/269/72485)全局状态管理维护与Native间通信。
是整个Call模块的核心。
详细代码可查看Demo源码。重点关注两个部分
- _onRtcListener = TUICallingListener(...): 定义了通话事件的监听器,通过 Method Channel 通知Native层动态控制 Call 模块所属的 ViewController(iOS)/Activity(Android) 的前端展示与否。
- Future<dynamic> _handleMessage(MethodCall call): 动态监听 Native 透传来的主动发起通话请求,来自 Call 模块的调用,主动发起通话。
2. 新建 `call_main.dart` 文件用于Call模块主入口。
该组件用于注入[音视频通话插件所需绑定的navigatorKey](https://cloud.tencent.com/document/product/269/72485#.E6.AD.A5.E9.AA.A41.EF.BC.9A.E5.BC.95.E5.85.A5-navigatorkey)。
详细代码可查看Demo源码。
#### 配置各个Flutter引擎的入口
开发完上述三个模块后现在可完成最终对外暴露的main方法作为Flutter引擎的入口。
1. 默认入口
打开 `lib/main.dart` 文件,将 `main()` 方法改成一个空 MaterialApp 即可。
该方法作为 Flutter Module 的默认入口在Flutter多引擎使用FlutterEngineGroup管理的背景下如果没有子Flutter Engine不设置任何entry point这个方法就不会被用到。
例如,在我们的场景中,这个默认 `main()` 方法就没有被用上。
```dart
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Container(),
));
}
```
2. 配置 Chat 模块的入口
使用 `@pragma('vm:entry-point')` 注解,将该方法标记为一个 `entry-point` 入口。方法名 `chatMain` 即该入口的名称在Native中也使用该名称创建对应Flutter引擎。
使用全局 `ChangeNotifierProvider` 状态管理,维护 `ChatInfoModel` 数据及业务逻辑。
```dart
@pragma('vm:entry-point')
void chatMain() {
// This call ensures the Flutter binding has been set up before creating the
// MethodChannel-based model.
WidgetsFlutterBinding.ensureInitialized();
final model = ChatInfoModel();
runApp(
ChangeNotifierProvider.value(
value: model,
child: const ChatAPP(),
),
);
}
```
3. 配置 Call 模块的入口
同理,该入口命名为 `callMain`
使用全局 `ChangeNotifierProvider` 状态管理,维护 `CallInfoModel` 数据及业务逻辑。
```dart
@pragma('vm:entry-point')
void callMain() {
// This call ensures the Flutter binding has been set up before creating the
// MethodChannel-based model.
WidgetsFlutterBinding.ensureInitialized();
final model = CallInfoModel();
runApp(
ChangeNotifierProvider.value(
value: model,
child: const CallAPP(),
),
);
}
```
至此Flutter Module部分Dart代码编写完成。
接下来,开始编写 Native 代码。
### iOS Native 开发
本文以 Swift 语言为例。
>?
>
> 以下代码结构,仅供参考,您可根据需要灵活组织。
进入您的iOS项目目录。
如果您现有的应用程序,假设叫做 `MyApp` 还没有Podfile请按照[CocoaPods入门指南](https://guides.cocoapods.org/using/using-cocoapods.html)将 `Podfile` 添加到项目中。
#### 引入 Flutter Module
请参考[此部分](#ios)将Flutter module引入您的原生应用程序中。建议采用方式一。
#### 在 iOS 项目中管理Flutter引擎
![](https://qcloudimg.tencent-cloud.cn/raw/e906c61c593195ca2310d08c1ac4a1f4.png)
**创建一个 `FlutterEngineGroup` Flutter 引擎组),统一管理多个引擎实例。**
`AppDelegate.swift` 文件中,添加如下代码:
```swift
@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
lazy var flutterEngines = FlutterEngineGroup(name: "chat.flutter.tencent", project: nil)
...
}
```
**创建一个用于管理Flutter引擎的单例对象。**
这个 Swift 单例对象,用于集中管理 Flutter 实例,并方便在项目中各处,直接调用。
Demo代码的逻辑是使用新的路由承载Chat的ViewControllerCall的ViewController通过present和dismiss动态弹窗维护。
新建 `FlutterUtils.swift` 文件编写代码。本部分详细代码可查看Demo源码。
重点关注:
- private override init(): 初始化各 Flutter 引擎实例注册Method Channel监听事件。
- func reportChatInfo(): 将用户登录信息和SDKAPPID透传至Flutter Module使Flutter层得以初始化并登录腾讯云IM。
- func launchCallFunc(): 用于拉起Call的Flutter页面可被Call模块收到通话邀请触发也可被Chat模块主动发起通话触发。
- func triggerNotification(msg: String): 将 iOS Native 层收到的离线推送消息点击事件及其包含的ext信息以 JSON String形式透传至 Flutter 层绑定的监听处理事件。用于处理离线推送点击跳转,例如至对应会话。
**监听及转发离线推送点击事件**
离线推送的初始化/Token上报/点击事件对应的会话跳转处理已在Flutter Chat模块中进行因此Native区域仅需透传点击通知事件的ext即可。
之所以这么做是因为点击通知事件已在Native被拦截消费Flutter层无法直接拿到必须经由Native转发。
`AppDelegate.swift` 文件中新增如下代码。具体代码可以参考Demo源码。
![](https://qcloudimg.tencent-cloud.cn/raw/9c816ae4745e5a8d2b9b1d64167e1fc5.png)
此时iOS Native层编写完成。
### Android Native 开发
本文以 Kotlin 语言为例。
>?
>
> 以下代码结构,仅供参考,您可根据需要灵活组织。
#### 引入 Flutter Module
请参考[此部分](#android)将Flutter module引入您的原生应用程序中。建议采用方式二。
#### 在 Android 项目中管理Flutter引擎
**创建一个用于管理Flutter引擎的单例对象。**
这个 Kotlin 单例对象,用于集中管理 Flutter 实例,并方便在项目中各处,直接调用。
新建 `FlutterUtils.kt` 文件,并定义 `FlutterUtils` 静态类。
```kotlin
@SuppressLint("StaticFieldLeak")
object FlutterUtils {}
```
**创建 `FlutterEngineGroup` Flutter 引擎组),统一管理多个引擎实例。**
`FlutterUtils.kt` 文件中,定义一个 `FlutterEngineGroup`及配套各个Flutter Engine实例和Method Channel并在初始化时将其初始化。
```kotlin
lateinit var context : Context
lateinit var flutterEngines: FlutterEngineGroup
private lateinit var chatFlutterEngine:FlutterEngine
private lateinit var callFlutterEngine:FlutterEngine
lateinit var chatMethodChannel: MethodChannel
lateinit var callMethodChannel: MethodChannel
// 初始化
flutterEngines = FlutterEngineGroup(context)
...
```
**继续完成该用于管理Flutter引擎的单例对象。**
Demo代码的逻辑是使用新的路由承载Chat和Call的Activity。
Chat的Activity由用户主动进入及退出Call的Activity由监听器或主动外呼自动导航进及返回出。
重点关注:
- fun init(): 初始化各 Flutter 引擎实例注册Method Channel监听事件。
- fun reportChatInfo(): 将用户登录信息和SDKAPPID透传至Flutter Module使Flutter层得以初始化并登录腾讯云IM。
- fun launchCallFunc(): 用于拉起Call的Flutter页面可被Call模块收到通话邀请触发也可被Chat模块主动发起通话触发。
- fun triggerNotification(msg: String): 将 iOS Native 层收到的离线推送消息点击事件及其包含的ext信息以 JSON String形式透传至 Flutter 层绑定的监听处理事件。用于处理离线推送点击跳转,例如至对应会话。
本单例 object 的详细代码可以参考Demo源码。
**在 总入口 `MyApplication` 中,初始化上述对象**
`MyApplication.kt` 文件中将全局context传入单例对象并执行初始化。
```kotlin
class MyApplication : MultiDexApplication() {
override fun onCreate() {
super.onCreate()
FlutterUtils.context = this // new
FlutterUtils.init() // new
}
}
```
**监听及转发离线推送点击事件**
离线推送的初始化/Token上报/点击事件对应的会话跳转处理已在Flutter Chat模块中进行因此Native区域仅需透传点击通知事件的ext即可。
之所以这么做是因为点击通知事件已在Native被拦截消费Flutter层无法直接拿到必须经由Native转发。
> 由于不同厂商的离线推送接入步骤不一致本文以OPPO为例全部厂商接入方案可查看[本文档](https://cloud.tencent.com/document/product/269/75428).
在腾讯云IM控制台中新增OPPO的推送证书`点击后续动作` 选择 `打开应用内指定页面``应用内页面` 以 `Activity` 方式配置一个用于处理离线推送信息的页面建议为应用首页。如我们的Demo配置为`com.tencent.chat.android.MainActivity`.
![](https://qcloudimg.tencent-cloud.cn/raw/c0f8737ce6fa484479ffc9a1bec6c9c0.png)
在上方控制台配置的用于离线推送的Activity文件中新增如下代码。
该代码的作用是当厂商拉起相应Activity时从Bundle中取出HashMap形式ext信息触发单例对象中的方法将这个信息手动转发至Flutter中。具体代码可以参考Demo源码。
![](https://qcloudimg.tencent-cloud.cn/raw/2ec45c1a8b3bd952bcb86a8095f91515.png)
此时Android Native层编写完成。
[](id:single)
## 方案二Flutter 单引擎方案
本方案将Chat模块和Call模块写在同一个Flutter引擎实例中。
![](https://qcloudimg.tencent-cloud.cn/raw/c6c52a028f5b86c88babe3074805b295.png)
这两个模块只能同时出现同时隐藏仅需维护一个Flutter引擎即可。
[![](https://qcloudimg.tencent-cloud.cn/raw/b622951f776a505e83f843de1f62fffc.png)](https://github.com/TencentCloud/tencentchat-add-flutter-to-app/tree/main/Single%20Flutter%20Engines)
### Flutter Module 开发
要将Flutter嵌入到现有应用程序中请首先创建一个Flutter模块。
在您项目的根目录外层,运行
```
cd some/path/
flutter create --template module tencent_chat_module
```
这会在 some/path/tencent_chat_module/ 创建一个 Flutter 模块项目。 在该目录中,您可以运行与在任何其他 Flutter 项目中相同的 Flutter 命令,例如 `flutter run --debug``flutter build ios`。 您还可以使用 Flutter 和 Dart 插件在 Android Studio, IntelliJ 或 VS Code 中运行该模块。 该项目在嵌入到现有应用程序之前包含模块的单视图示例版本,这对于测试代码的仅 Flutter 部分很有用。
`tencent_chat_module` 模块目录结构类似于普通的 Flutter 应用程序:
```
tencent_chat_module/
├── .ios/
│ ├── Runner.xcworkspace
│ └── Flutter/podhelper.rb
├── lib/
│ └── main.dart
├── test/
└── pubspec.yaml
```
现在,我们可以在 `lib/` 中,编写代码了。
#### main.dart
修改 `main.dart` 文件,引入[TUIKit](https://cloud.tencent.com/document/product/269/70747), [离线推送插件](https://cloud.tencent.com/document/product/269/74605)及[音视频通话插件](https://cloud.tencent.com/document/product/269/72485)。
全局状态我们的IM SDK及Method Channel与Native通信状态管理于 `ChatInfoModel` 中。
接收到Native传来的用户信息及SDKAPPID后调用 `_coreInstance.init()``_coreInstance.login() ` 初始化并登录腾讯云IM。并初始化音视频推送插件及离线推送插件完成推送Token上报。
> ?
>
> 请根据 [离线推送接入指引](https://cloud.tencent.com/document/product/269/74605)完成厂商离线推送功能接入才可正常上报推送Token使用推送功能。
对于音视频通话插件,需要关注:
- 监听收到新的通话邀请时通过调用Native方法让Native检测用户当前是否在本Flutter模块页面如果不在需要强制将前端页面调整至本模块以展示来电页面。
对于离线推送插件,需要关注:
- 点击通知处理事件来自Native透传从 Map 中取出数据,跳转至对应的子模块,如某个具体会话。
完成首页的制作,在未登录时展示加载动画;登录成功后,展示会话列表页面。
此外,还需要在这里,完成 `didChangeAppLifecycleState` 监听与前后台切换事件上报,详情请查看[离线推送插件文档步骤5](https://cloud.tencent.com/document/product/269/74605#.E6.AD.A5.E9.AA.A45.EF.BC.9A.E5.89.8D.E5.90.8E.E5.8F.B0.E5.88.87.E6.8D.A2.E7.9B.91.E5.90.AC.3Ca-id.3D.22step_5.22.3E.3C.2Fa.3E)。
详细代码可查看Demo源码。
#### 其他TUIKit模块引入
1. 新建 `push.dart` 文件,用于单例管理 [离线推送插件](https://cloud.tencent.com/document/product/269/74605) 能力。用于获取并上报Token/获取推送权限等操作。详细代码可查看Demo源码。
2. 新建 `conversation.dart` 文件用于承载TUIKit的会话模块组件 `TIMUIKitConversation`。详细代码可查看Demo源码。
3. 新建 `chat.dart` 文件用于承载TUIKit的历史消息列表和发送消息模块组件 `TIMUIKitChat`
该页面还有跳转至 Profile 及 Group Profile 页面的能力。
详细代码可查看Demo源码。
4. 新建 `user_profile.dart` 文件用于承载TUIKit的用户信息及关系链管理模块组件 `TIMUIKitProfile`。详细代码可查看Demo源码。
5. 新建 `group_profile.dart` 文件用于承载TUIKit的群信息及群管理模块组件 `TIMUIKitGroupProfile`。详细代码可查看Demo源码。
至此统一的Flutter Module开发完成。
### iOS Native 开发
本文以 Swift 语言为例。
>?
>
> 以下代码结构,仅供参考,您可根据需要灵活组织。
进入您的iOS项目目录。
如果您现有的应用程序,假设叫做 `MyApp` 还没有Podfile请按照[CocoaPods入门指南](https://guides.cocoapods.org/using/using-cocoapods.html)将 `Podfile` 添加到项目中。
#### 引入 Flutter Module
请参考[此部分](#ios)将Flutter module引入您的原生应用程序中。建议采用方式一。
#### 在 iOS 项目中管理Flutter引擎
**创建一个FlutterEngine。**
创建FlutterEngine的适当位置特定于您的主应用程序入口。作为一个例子我们演示了如何在 `AppDelegate` 中的app启动时创建一个FlutterEngine并公开为一个属性。
```swift
import UIKit
import Flutter
import FlutterPluginRegistrant
@UIApplicationMain
class AppDelegate: FlutterAppDelegate { // More on the FlutterAppDelegate.
lazy var flutterEngine = FlutterEngine(name: "tencent cloud chat")
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Runs the default Dart entrypoint with a default Flutter route.
flutterEngine.run();
GeneratedPluginRegistrant.register(with: self.flutterEngine);
return super.application(application, didFinishLaunchingWithOptions: launchOptions);
}
}
```
**创建一个用于管理Flutter引擎的单例对象。**
这个 Swift 单例对象,用于集中管理 Flutter Method Channel并提供一系列与 Flutter Module 通信的方法,方便在项目中各处,直接调用。
这些方法包括:
- private override init(): 初始化 Method Channel并为其绑定事件监听方法。
- func reportChatInfo(): 将用户登录信息和SDKAPPID透传至Flutter Module使Flutter层得以初始化并登录腾讯云IM。
- func launchChatFunc(): 拉起或导航至 Flutter Module 所在 ViewController。
- func triggerNotification(msg: String): 将 iOS Native 层收到的离线推送消息点击事件及其包含的ext信息以 JSON String形式透传至 Flutter 层绑定的监听处理事件。用于处理离线推送点击跳转,例如至对应会话。
详细代码可查看Demo源码。
**监听及转发离线推送点击事件**
离线推送的初始化/Token上报/点击事件对应的会话跳转处理已在Flutter Chat模块中进行因此Native区域仅需透传点击通知事件的ext即可。
之所以这么做是因为点击通知事件已在Native被拦截消费Flutter层无法直接拿到必须经由Native转发。
`AppDelegate.swift` 文件中新增如下代码。具体代码可以参考Demo源码。
![](https://qcloudimg.tencent-cloud.cn/raw/0ea48d21696a1e696ab98091983168f9.png)
此时iOS Native层编写完成。
### Android Native 开发
本文以 Kotlin 语言为例。
>?
>
> 以下代码结构,仅供参考,您可根据需要灵活组织。
#### 引入 Flutter Module
请参考[此部分](#android)将Flutter module引入您的原生应用程序中。建议采用方式二。
#### 在 Android 项目中管理Flutter引擎
**创建一个用于管理Flutter引擎的单例对象。**
这个 Kotlin 单例对象,用于集中管理 Flutter Method Channel并提供一系列与 Flutter Module 通信的方法,方便在项目中各处,直接调用。
新建 `FlutterUtils.kt` 文件,并定义 `FlutterUtils` 静态类。
```kotlin
@SuppressLint("StaticFieldLeak")
object FlutterUtils {}
```
**创建一个 `FlutterEngine` Flutter 引擎)。**
`FlutterUtils.kt` 文件中,定义一个 `FlutterEngine`,并在初始化时,将其初始化。
```kotlin
lateinit var context : Context
private lateinit var flutterEngine:FlutterEngine
// 初始化
flutterEngine = FlutterEngine(context)
```
**继续完成该用于管理Flutter引擎的单例对象。**
Demo代码的逻辑是使用新的路由承载Chat和Call的Activity。
Chat的Activity由用户主动进入及退出Call的Activity由监听器或主动外呼自动导航进及返回出。
重点关注:
- fun init(): 初始化 Method Channel并为其绑定事件监听方法。
- fun reportChatInfo(): 将用户登录信息和SDKAPPID透传至Flutter Module使Flutter层得以初始化并登录腾讯云IM。
- fun launchChatFunc(): 拉起或导航至 Flutter Module 所在 ViewController。
- fun triggerNotification(msg: String): 将 iOS Native 层收到的离线推送消息点击事件及其包含的ext信息以 JSON String形式透传至 Flutter 层绑定的监听处理事件。用于处理离线推送点击跳转,例如至对应会话。
本单例 object 的详细代码可以参考Demo源码。
**在 总入口 `MyApplication` 中,初始化上述对象**
`MyApplication.kt` 文件中将全局context传入单例对象并执行初始化。
```kotlin
class MyApplication : MultiDexApplication() {
override fun onCreate() {
super.onCreate()
FlutterUtils.context = this // new
FlutterUtils.init() // new
}
}
```
**监听及转发离线推送点击事件**
离线推送的初始化/Token上报/点击事件对应的会话跳转处理已在Flutter Chat模块中进行因此Native区域仅需透传点击通知事件的ext即可。
之所以这么做是因为点击通知事件已在Native被拦截消费Flutter层无法直接拿到必须经由Native转发。
> 由于不同厂商的离线推送接入步骤不一致本文以OPPO为例全部厂商接入方案可查看[本文档](https://cloud.tencent.com/document/product/269/75428).
在腾讯云IM控制台中新增OPPO的推送证书`点击后续动作` 选择 `打开应用内指定页面``应用内页面` 以 `Activity` 方式配置一个用于处理离线推送信息的页面建议为应用首页。如我们的Demo配置为`com.tencent.chat.android.MainActivity`.
![](https://qcloudimg.tencent-cloud.cn/raw/c0f8737ce6fa484479ffc9a1bec6c9c0.png)
在上方控制台配置的用于离线推送的Activity文件中新增如下代码。
该代码的作用是当厂商拉起相应Activity时从Bundle中取出HashMap形式ext信息触发单例对象中的方法将这个信息手动转发至Flutter中。具体代码可以参考Demo源码。
![](https://qcloudimg.tencent-cloud.cn/raw/2ec45c1a8b3bd952bcb86a8095f91515.png)
此时Android Native层编写完成。
[](id:native)
## 附加方案:在 Native 层初始化并登录腾讯云IM
有的时候对于Chat和Call模块能力您希望对于高频的简单应用场景能深入嵌入您现有的业务逻辑中。
例如对于游戏场景,在对局内,希望能直接发起会话。
而您的完整功能Chat模块使用Flutter实现仅是您APP中一个重要性较低的子模块因此不希望一上来就启动一个完整的Flutter Module。
这个时候您可以在Native层调用腾讯云IM Native SDK的初始化及登录方法此后便可在您需要的高频简单场景直接使用腾讯云IM Native SDK构建 In-App Chat 能力。
>?
> 当然,在此种情况下,您也可以选择提前先在 Flutter 初始化并登录腾讯云IM此时您将不再需要在 Native 层再次初始化并登录。两端仅需初始化并登录一次,即可在双端都能使用。
由于Flutter SDK已自带Native SDK您不需要在Native层再次引入即可直接使用。
### Native初始化并登录
以 iOS Swift 代码为例,演示如何在 Native 层,初始化并登录。
```swift
import ImSDK_Plus
func initTencentChat(){
if(isLoginSuccess == true){
return
}
let data = V2TIMManager.sharedInstance().initSDK( 您的SDKAPPID , config: nil);
if (data == true){
V2TIMManager.sharedInstance().login(
chatInfo.userID,
userSig: chatInfo.userSig,
succ: {
self.isLoginSuccess = true
self.reportChatInfo()
},
fail: onLoginFailed()
)
}
}
```
此后,在 Native 层面便可直接使用Native SDK搭建您的业务功能模块。详情可查阅 [iOS 快速入门]() 或 [Android 快速入门](https://cloud.tencent.com/document/product/269/36838)。
### 初始化 Flutter TUIKit
如果您已在 Native 层完成初始化并登录,您不需要再次在 Flutter 层再次执行,但需要调用 TUIKit的 `_coreInstance.setDataFromNative()`,将当前用户信息传入。
```dart
final CoreServicesImpl _coreInstance = TIMUIKitCore.getInstance();
_coreInstance.setDataFromNative(userId: chatInfo?.userID ?? "");
```
**更详细代码请查阅我们的Demo 源码。**
[![](https://qcloudimg.tencent-cloud.cn/raw/b622951f776a505e83f843de1f62fffc.png)](https://github.com/TencentCloud/tencentchat-add-flutter-to-app/tree/main/Initialize%20from%20Native)
-----
至此腾讯云IM Flutter - Native 混合开发方式已全部介绍完成。
您可以基于本文档给出的方案,快速在您现有的原生开发 Android/iOS APP 中,使用 Flutter SDK使用同一套Flutter代码快速植入 Chat 和 Call 模块能力。
如果您还有任何疑问,欢迎随时联系我们。
![](https://qcloudimg.tencent-cloud.cn/raw/eacb194c77a76b5361b2ae983ae63260.png)
## Reference
1. [Integrate a Flutter module into your Android project](https://docs.flutter.dev/development/add-to-app/android/project-setup).
2. [Integrate a Flutter module into your iOS project](https://docs.flutter.dev/development/add-to-app/ios/project-setup).
3. [Adding a Flutter screen to an iOS app](https://docs.flutter.dev/development/add-to-app/ios/add-flutter-screen?tab=no-engine-vc-swift-tab).
4. [Multiple Flutter screens or views](https://docs.flutter.dev/development/add-to-app/multiple-flutters).