tencent cloud

文档反馈

快速入门(快速集成Flutter至您现用应用)

最后更新时间:2023-02-24 15:00:55


    通过阅读本文,你可以了解在您现有的 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_im_sdk_plugin 5.0 及以上版本, tim_ui_kit 0.2 及以上版本。
    说明:

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

    前置知识点

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

    腾讯云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的情况下构建主机应用程序。然后,您可以从本地或远程存储库中分发构件。

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

    具体步骤:

    在您的Flutter module中,运行:

    flutter build aar
    

    然后,按照屏幕上的说明进行集成。

    您的应用程序现在将Flutter模块作为依赖项包括在内。

    Android方式二:依赖Flutter module源代码

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

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

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

    具体步骤:

    将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
    

    在您应用中的 app/build.gradle => dependencies 中引入对Flutter module的 implementation:

    dependencies {
      implementation project(':flutter')
    }
    

    您的应用程序现在将Flutter模块作为依赖项包括在内。

    将 Flutter 模块添加至 iOS 项目中

    详细学习

    有两种方法可以在现有应用程序中嵌入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,调用 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 所需的权限声明,包括麦克风权限/相机权限/相册权限。

    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/.

    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模块。有关两个模块的介绍,请看此处

    对于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 _handleMessage(MethodCall call): 动态监听 Native 透传来的事件,包括登录信息及点击推送事件。
      • Future handleClickNotification(Map<string, dynamic=""> msg): 点击通知处理事件,来自Native透传,从 Map 中取出数据,跳转至对应的子模块,如某个具体会话。
      • Future initChat(): 初始化腾讯云IM/登录腾讯云IM/并完成离线推送的初始化及Token上报。该方法使用线程锁机制,保证同时只能执行一个,并在初始化成功后,不重复执行。
    说明:

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

    1. 新建 chat_main.dart 文件,用于Chat模块主入口。

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

    3. 新建 conversation.dart 文件,用于承载TUIKit的会话模块组件 TIMUIKitConversation。详细代码可查看Demo源码。

    4. 新建 chat.dart 文件,用于承载TUIKit的历史消息列表和发送消息模块组件 TIMUIKitChat
      该页面还有跳转至 Profile 及 Group Profile 页面的能力。
      详细代码可查看Demo源码。

    5. 新建 user_profile.dart 文件,用于承载TUIKit的用户信息及关系链管理模块组件 TIMUIKitProfile。详细代码可查看Demo源码。

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

    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.
      </string,>
    联系我们

    联系我们,为您的业务提供专属服务。

    技术支持

    如果你想寻求进一步的帮助,通过工单与我们进行联络。我们提供7x24的工单服务。

    7x24 电话支持