无 UI 集成方案

服务端 API

诚邀爱技术、爱分享的你,成为文档内容共建者> HOT
文档中心 > 即时通信 IM > 快速入门 > 快速入门(将 Flutter 添加至您现有应用)

通过阅读本文,您可以了解在您现有的 Android / iOS 原生开发项目中,集成腾讯云 IM Flutter 的方法。

有的时候,使用 Flutter 重写您现有的应用程序是不现实的。如果您想在现有 APP 中,使用腾讯云 IM 的能力,推荐采用混合开发方案,即将 Flutter 模块,嵌入您的原生开发 APP 项目中。

可在很大程度上,降低您的工作量,快速在双端原生 APP 中,植入 IM 通信能力。

环境要求

环境 版本
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_cloud_chat_sdk 5.0 及以上版本, tencent_cloud_chat_uikit 1.0 及以上版本。

快速了解

说明:

对于以上的 Demo 项目,源代码可在我们的 GitHub 仓库 中找到,欢迎查阅。

前置知识点

开始之前,您需要了解腾讯云 IM Flutter SDK 及 TUIKit 的用法,及 Flutter-原生混合开发原理。

前序工作

  1. 您已 注册腾讯云 帐号,并完成 实名认证
  2. 参照 创建并升级应用 创建应用,并记录好 SDKAppID
  3. IM 控制台 选择您的应用,在左侧导航栏依次点击 辅助工具->UserSig 生成&校验 ,创建两个 UserID 及其对应的 UserSig,复制UserID签名(Key)UserSig这三个,后续登录时会用到。
说明:

该账户仅限开发测试使用。应用上线前,正确的 UserSig 签发方式是由服务器端生成,并提供面向 App 的接口,在需要 UserSig 时由 App 向业务服务器发起请求获取动态 UserSig。更多详情请参见 服务端生成 UserSig

腾讯云 IM

总体入门

在开始前,您首先需要了解腾讯云 IM Flutter 的 SDK 构成及使用方式。

主要包括两个 SDK:无 UI 版本含UI组件库。本文将以 含UI组件库(TUIKit) 为例,介绍混合开发方案。

关于腾讯云 IM Flutter 详细用法,可从我们的 快速入门文档 看起。

两个模块

腾讯云 IM 主要有两个部分,包括 Chat 聊天模块 和 Call 通话模块。

  • Chat 聊天模块主要包括消息收发、会话管理、用户关系管理等。
  • Call 通话模块主要包括音视频通话,包括一对一通话和群组多人通话。

Flutter 混合开发

核心原理是,将 module 形式的 Flutter 项目,打包成 Native 端的可执行程序,嵌入 Native 项目中。因 Flutter module 可以通用,因此仅需编写一次 Flutter module,即可嵌入 Android/iOS APP 中。

当您现有应用需要展示腾讯云IM相关页面时,可加载对应用于承载 Flutter 的 Activity(Android)或 ViewController(iOS)。

当需要两端通信时,如传递当前用户信息,传递音视频通话数据,触发离线推送数据,可采用 Method Channel 方式进行。触发另一端的方法使用 invokeMethod,监听另一端发来的方法调用使用 预挂载的 Method Channel 监听器

将 Flutter 模块添加至 Android 项目中

详细学习

将 Flutter module 添加为 Gradle 中现有应用程序的依赖项。有两种方式可以实现这一点。

Android方式一:依赖 Android Archive (AAR)

AAR 机制创建通用的 Android AAR 作为打包 Flutter module 的中介。如果您经常构建,它会增加一个构建步骤。

该选项将 Flutter 库打包为由 AAR 和 POMS 构件组成的通用本地 Maven 存储库。此选项允许您的团队在不安装 Flutter SDK 的情况下构建主机应用程序。然后,您可以从本地或远程存储库中分发构件。

因此,建议在线上生产环境,使用本方案。

具体步骤:

  1. 在您的Flutter module中,运行:
    flutter build aar
  2. 然后,按照屏幕上的说明进行集成。
  3. 您的应用程序现在将 Flutter 模块作为依赖项包括在内。
Android 方式二:依赖Flutter module源代码

源代码子项目机制是一个方便的一键构建过程,但需要 Flutter SDK。这是 Android Studio IDE 插件使用的机制。

此方式可为您的 Android 项目和 Flutter 项目实现一步构建。当您同时处理两个部分并快速迭代时,此选项很方便,但您的团队必须安装 Flutter SDK 才能构建应用程序。

因此,建议在开发测试环境,使用本方案。

具体步骤:

  1. 将 Flutter module 作为一个子项目,添加至宿主 APP 的 settings.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
  2. 在您应用中的 app/build.gradle => dependencies 中引入对Flutter module的 implementation
    dependencies {
      implementation project(':flutter')
    }
  3. 您的应用程序现在将Flutter模块作为依赖项包括在内。

将 Flutter 模块添加至 iOS 项目中

详细学习

有两种方法可以在现有应用程序中嵌入 Flutter。

iOS方式一:嵌入 CocoaPods 和 Flutter SDK 集成

使用 CocoaPods 依赖项管理器并安装 Flutter SDK。这种方法要求每个从事项目工作的开发人员都有一个本地安装的 Flutter SDK 版本。

只需在 Xcode 中构建您的应用程序,即可自动运行脚本来嵌入您的 DART 和插件代码。这允许快速迭代最新版本的颤振模块,而无需在 Xcode 之外运行其他命令。

因此,建议在开发测试环境,使用本方案。

具体步骤:

  1. 将以下代码添加到 Podfile 中:
    // 上一步构建的Flutter Module的路径
    flutter_chat_application_path = '../tencent_chat_module'
    
    load File.join(flutter_chat_application_path, '.ios', 'Flutter', 'podhelper.rb')
  2. 对于每个需要嵌入 Flutter 的 Podfile target,调用 install_all_flutter_pods(flutter_chat_application_path)
    target 'MyApp' do
      install_all_flutter_pods(flutter_chat_application_path)
    end
  3. 在 Podfile 的 post_install 块中,调用 flutter_post_install(installer),并完成 腾讯云IM TUIKit 所需的权限声明,包括麦克风权限/相机权限/相册权限。
    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.

因此,建议在线上环境,使用本方案。

具体步骤:

  1. 在您的Flutter module中,运行如下代码。下面的示例,假设您想要将framework生成到 some/path/MyApp/Flutter/
    flutter build ios-framework --output=some/path/MyApp/Flutter/
  2. 在 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 模块。有关两个模块的介绍,请看此处

对于 Flutter 引擎的创建管理,目前两种方式:单 Flutter 引擎及多 Flutter 引擎。

引擎模式 介绍 优点 缺点 Demo 源码下载
Flutter单引擎 Chat 模块和 Call 模块在同一个 Flutter 引擎中承载。 方便,所有 Flutter 代码统一维护。 由于 Call 插件,在有电话呼入时,需要自动展示来电页面。如果在同一个引擎中,需要强制跳转至 Flutter 所在页面,体验较差。 点击下载
Flutter多引擎 Chat 模块和 Call 模块分别承载于不同的 Flutter 引擎中,使用 Flutter 引擎组来统一管理这两个引擎。 Call 插件独立存在于一个 Flutter 引擎中,独立页面控制,来电时,直接将该页面弹窗即可,不影响用户当前所在页面,体验较好。 通话模块无法最小化成浮窗形式。 点击下载

此外,我们还提供,将腾讯云 IM Native SDK 与 Flutter SDK 结合使用的方案,适用场景和步骤介绍可查看这里Demo 源码下载

方案一:Flutter 多引擎方案(推荐)

本方案中,Chat 和 Call 模块分别独立于不同的Flutter引擎。

使用多个 Flutter 引擎的优点是,每个实例都是独立的,并维护其自己的内部导航堆栈、UI和应用程序状态。这简化了整个应用程序代码的状态保持责任,并提高了模块化能力。

在 Android 和 iOS 上添加多个 Flutter 引擎,主要基于一个 FlutterEngineGroup 类(Android API、iOS API)来构造并管理多个 FlutterEngine(Flutter 引擎)。

在我们的项目中,我们基于一个统一的 FlutterEngineGroup,来管理两个 FlutterEngine(Flutter 引擎),分别用于承载 Chat 和 Calling 模块。

Flutter Module 开发

要将 Flutter 嵌入到现有应用程序中,请首先创建一个 Flutter 模块。

在您项目的根目录外层,运行

cd some/path/
flutter create --template module tencent_chat_module

这会在 some/path/tencent_chat_module/ 创建一个 Flutter 模块项目。 在该目录中,您可以运行与在任何其他 Flutter 项目中相同的 Flutter 命令,例如 flutter run --debugflutter 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与原生应用通信规范。

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上报。该方法使用线程锁机制,保证同时只能执行一个,并在初始化成功后,不重复执行。
      说明:

      请根据 离线推送接入指引,完成厂商离线推送功能接入,才可正常上报推送 Token,使用推送功能。

  2. 新建 chat_main.dart 文件,用于 Chat 模块主入口。
    • 该页面也是 Flutter Chat 模块的首页。
    • 在 Demo 中,该页面在未登录前为加载状态,登录后展示会话列表。
    • 此外,还需要在这里,完成 didChangeAppLifecycleState 监听与前后台切换事件上报,详情请查看 离线推送插件文档步骤5
    • 详细代码可查看 Demo 源码。
  3. 新建 push.dart 文件,用于单例管理 离线推送插件 能力。用于获取并上报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 模块

该模块用于承载音视频通话能力,该能力由 音视频通话插件 提供。

该模块的核心是,监听收到新的通话邀请时,通过调用Native方法,自动弹出通话页面;并接受 Chat 模块经由Native转发来的通话请求,主动发起通话。

首先编写 IM 引擎。本模块所有代码及文件,均在 lib/call 目录下。

  1. 新建全局状态管理 Model,名为 model.dart
    该 Model 用于挂载初始化并管理 音视频通话插件,全局状态管理,维护与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。详细代码可查看 Demo 源码。

配置各个 Flutter 引擎的入口

开发完上述三个模块后,现在可完成最终对外暴露的 main 方法,作为 Flutter 引擎的入口。

  1. 默认入口。打开 lib/main.dart 文件,将 main() 方法改成一个空 MaterialApp 即可。该方法作为 Flutter Module 的默认入口,在Flutter多引擎,使用 FlutterEngineGroup 管理的背景下,如果没有子 Flutter Engine 不设置任何 entry point,这个方法就不会被用到。例如,在我们的场景中,这个默认 main() 方法就没有被用上。

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(MaterialApp(
    title: 'Flutter Demo',
    theme: ThemeData(
      primarySwatch: Colors.blue,
    ),
    home: Container(),
  ));
}

  1. 配置 Chat 模块的入口。使用 @pragma('vm:entry-point') 注解,将该方法标记为一个 entry-point 入口。方法名 chatMain 即该入口的名称,在 Native 中,也使用该名称,创建对应 Flutter 引擎。使用全局 ChangeNotifierProvider 状态管理,维护 ChatInfoModel 数据及业务逻辑。

@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(),
    ),
  );
}

  1. 配置 Call 模块的入口。同理,该入口命名为 callMain。使用全局 ChangeNotifierProvider 状态管理,维护 CallInfoModel 数据及业务逻辑。

@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 入门指南Podfile 添加到项目中。

引入 Flutter Module

请参考 此部分,将 Flutter module 引入您的原生应用程序中。建议采用方式一。

在 iOS 项目中,管理 Flutter 引擎

创建一个 FlutterEngineGroup (Flutter 引擎组),统一管理多个引擎实例。

AppDelegate.swift 文件中,添加如下代码:

@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
  lazy var flutterEngines = FlutterEngineGroup(name: "chat.flutter.tencent", project: nil)
  ...
}

创建一个用于管理Flutter引擎的单例对象。

这个 Swift 单例对象,用于集中管理 Flutter 实例,并方便在项目中各处,直接调用。

Demo 代码的逻辑是,使用新的路由,承载 Chat 的 ViewController;Call 的 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 源码。

此时,iOS Native 层编写完成。

Android Native 开发

本文以 Kotlin 语言为例。

说明:

以下代码结构,仅供参考,您可根据需要灵活组织。

引入 Flutter Module

请参考 此部分,将 Flutter module 引入您的原生应用程序中。建议采用方式二。

在 Android 项目中,管理Flutter引擎

创建一个用于管理Flutter引擎的单例对象。

这个 Kotlin 单例对象,用于集中管理 Flutter 实例,并方便在项目中各处,直接调用。

新建 FlutterUtils.kt 文件,并定义 FlutterUtils 静态类。

@SuppressLint("StaticFieldLeak")
object FlutterUtils {}

创建 FlutterEngineGroup (Flutter 引擎组),统一管理多个引擎实例。

FlutterUtils.kt 文件中,定义一个 FlutterEngineGroup,及配套各个 Flutter Engine 实例和 Method Channel,并在初始化时,将其初始化。

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 代码的逻辑是,使用新的路由,承载 Cha t和 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传入单例对象,并执行初始化。

class MyApplication : MultiDexApplication() {

    override fun onCreate() {
        super.onCreate()
        FlutterUtils.context = this // new
        FlutterUtils.init()         // new
    }
}

监听及转发离线推送点击事件

离线推送的初始化/Token上报/点击事件对应的会话跳转处理,已在 Flutter Chat 模块中进行,因此,Native 区域,仅需透传点击通知事件的ext即可。

之所以这么做,是因为点击通知事件已在 Native 被拦截消费,Flutter 层无法直接拿到,必须经由 Native 转发。

说明:

由于不同厂商的离线推送接入步骤不一致,本文以 OPPO 为例,全部厂商接入方案,可查看 本文档.

在腾讯云 IM 控制台中,新增 OPPO 的推送证书,点击后续动作 选择 打开应用内指定页面应用内页面Activity 方式,配置一个用于处理离线推送信息的页面,建议为应用首页。如,我们的 Demo 配置为:com.tencent.chat.android.MainActivity.

在上方控制台配置的用于离线推送的 Activity 文件中,新增如下代码。

该代码的作用是,当厂商拉起相应 Activity 时,从 Bundle 中取出 HashMap 形式 ext 信息,触发单例对象中的方法,将这个信息,手动转发至 Flutter 中。具体代码,可以参考 Demo 源码。

此时,Android Native 层编写完成。

方案二:Flutter 单引擎方案

本方案,将 Chat 模块和 Call 模块,写在同一个 Flutter 引擎实例中。

这两个模块只能同时出现同时隐藏,仅需维护一个 Flutter 引擎即可。

Flutter Module 开发

要将 Flutter 嵌入到现有应用程序中,请首先创建一个 Flutter 模块。

在您项目的根目录外层,运行

cd some/path/
flutter create --template module tencent_chat_module

这会在 some/path/tencent_chat_module/ 创建一个 Flutter 模块项目。 在该目录中,您可以运行与在任何其他 Flutter 项目中相同的 Flutter 命令,例如 flutter run --debugflutter 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, 离线推送插件音视频通话插件

全局状态,我们的 IM SDK 及 Method Channel 与 Native 通信状态,管理于 ChatInfoModel 中。

接收到 Native 传来的用户信息及 SDKAPPID 后,调用 _coreInstance.init()_coreInstance.login() 初始化并登录腾讯云IM。并初始化音视频推送插件及离线推送插件,完成推送 Token 上报。

说明:

请根据 离线推送接入指引,完成厂商离线推送功能接入,才可正常上报推送 Token,使用推送功能。

  • 对于音视频通话插件,需要关注:监听收到新的通话邀请时,通过调用 Native 方法,让 Native 检测用户当前是否在本 Flutter 模块页面,如果不在,需要强制将前端页面调整至本模块,以展示来电页面。
  • 对于离线推送插件,需要关注:点击通知处理事件,来自 Native 透传,从 Map 中取出数据,跳转至对应的子模块,如某个具体会话。

完成首页的制作,在未登录时展示加载动画;登录成功后,展示会话列表页面。

此外,还需要在这里,完成 didChangeAppLifecycleState 监听与前后台切换事件上报,详情请查看 离线推送插件文档步骤5

详细代码可查看 Demo 源码。

其他 TUIKit 模块引入

  1. 新建 push.dart 文件,用于单例管理 离线推送插件 能力。用于获取并上报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入门指南Podfile 添加到项目中。

引入 Flutter Module

请参考 此部分,将 Flutter module 引入您的原生应用程序中。建议采用方式一。

在 iOS 项目中,管理 Flutter 引擎

创建一个 FlutterEngine。

创建 FlutterEngine 的适当位置特定于您的主应用程序入口。作为一个例子,我们演示了如何在 AppDelegate 中的app启动时创建一个 FlutterEngine,并公开为一个属性。

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 源码。

此时,iOS Native 层编写完成。

Android Native 开发

本文以 Kotlin 语言为例。

说明:

以下代码结构,仅供参考,您可根据需要灵活组织。

引入 Flutter Module

请参考 此部分,将 Flutter module 引入您的原生应用程序中。建议采用方式二。

在 Android 项目中,管理 Flutter 引擎

创建一个用于管理 Flutter 引擎的单例对象。

这个 Kotlin 单例对象,用于集中管理 Flutter Method Channel,并提供一系列与 Flutter Module 通信的方法,方便在项目中各处,直接调用。

新建 FlutterUtils.kt 文件,并定义 FlutterUtils 静态类。

@SuppressLint("StaticFieldLeak")
object FlutterUtils {}

创建一个 FlutterEngine (Flutter 引擎)。

FlutterUtils.kt 文件中,定义一个 FlutterEngine,并在初始化时,将其初始化。

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传入单例对象,并执行初始化。

class MyApplication : MultiDexApplication() {

    override fun onCreate() {
        super.onCreate()
        FlutterUtils.context = this // new
        FlutterUtils.init()         // new
    }
}

监听及转发离线推送点击事件

离线推送的初始化/Token上报/点击事件对应的会话跳转处理,已在 Flutter Chat 模块中进行,因此,Native 区域,仅需透传点击通知事件的 ext 即可。

之所以这么做,是因为点击通知事件已在 Native 被拦截消费,Flutter 层无法直接拿到,必须经由 Native 转发。

说明:

由于不同厂商的离线推送接入步骤不一致,本文以 OPPO 为例,全部厂商接入方案,可查看 本文档

在腾讯云 IM 控制台中,新增 OPPO 的推送证书,点击后续动作 选择 打开应用内指定页面应用内页面Activity 方式,配置一个用于处理离线推送信息的页面,建议为应用首页。如,我们的 Demo 配置为:com.tencent.chat.android.MainActivity

在上方控制台配置的用于离线推送的 Activity 文件中,新增如下代码。

该代码的作用是,当厂商拉起相应 Activity 时,从 Bundle 中取出 HashMap 形式 ext 信息,触发单例对象中的方法,将这个信息,手动转发至 Flutter 中。具体代码,可以参考 Demo 源码。

此时,Android 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 层,初始化并登录。

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 快速入门

初始化 Flutter TUIKit

如果您已在 Native 层完成初始化并登录,您不需要再次在 Flutter 层再次执行,但需要调用 TUIKit 的 _coreInstance.setDataFromNative(),将当前用户信息传入。

final CoreServicesImpl _coreInstance = TIMUIKitCore.getInstance();
_coreInstance.setDataFromNative(userId: chatInfo?.userID ?? "");

更详细代码,请查阅我们的 Demo 源码。

联系我们

至此,腾讯云 IM Flutter - Native 混合开发方式已全部介绍完成。

您可以基于本文档给出的方案,快速在您现有的原生开发 Android/iOS APP 中,使用 Flutter SDK,使用同一套 Flutter 代码,快速植入 Chat 和 Call 模块能力。

如果您还有任何疑问,欢迎随时联系我们。

Reference

  1. Integrate a Flutter module into your Android project.
  2. Integrate a Flutter module into your iOS project.
  3. Adding a Flutter screen to an iOS app.
  4. Multiple Flutter screens or views.
目录