现有 Flutter 工程要塞一屏纯 Android 的设置页。稿是 Jetpack Compose 画的,交互也按原生来,不是列表中间嵌一块地图那么轻。官方把这条路径写在 Launching a Jetpack Compose activity:Flutter 侧发一个 MethodChannel,Android 侧再 startActivity 打开第二块 Activity。整页由 Android 自己跑,Kotlin 里写界面。需要和 Dart 说话时再走同一条通道。
若只是把某个原生控件嵌进 Flutter 的 widget 树(地图、WebView 一类),那是另一页 Hosting native Android views 的事。嵌控件还要在 Texture layer、Hybrid composition 这些实现里做取舍。开 Activity 则是再起一个全屏页面,两边别混用。
复杂结构体怎么在 Dart 和 Kotlin 之间序列化,官方示例里点到可以用 Pigeon 生成两边匹配的类。那是类型生成,和「什么时候该换代码生成工具」不是同一篇选型讨论,差别见站内这篇:Flutter 插件还在手写 MethodChannel,官方说什么时候该换 Pigeon 或 FFIgen。下面只按 compose-activity 页改工程。
Dart 侧:通道名、方法名、参数形状
示例新建一个 MethodChannel,名字是 com.example.flutter_android_activity(可改,但必须和 Kotlin 里的 CHANNEL 一致)。某个按钮的回调里调用 invokeMethod,方法名示例是 launchActivity(同样必须和原生 call.method 对上)。第三个要对齐的是数据结构:示例传一个带 message 键的 map,原生侧按这个形状去取。文档原话是可以传任意原始类型。
const platformMethodChannel = MethodChannel(
'com.example.flutter_android_activity',
);
void _launchAndroidActivity() {
platformMethodChannel.invokeMethod(
'launchActivity',
{'message': 'Hello from Flutter'},
);
}
Flutter 跑在 Android 上时,整个应用已经占满一块 Activity。原生回调的工作就是再拉起第二块 Activity,而不是在当前页里塞 Compose。文档也写了:并非所有 Android Activity 都用 Jetpack Compose,但这篇教程默认你要上 Compose,所以才会去改 buildFeatures 和 BOM。
Android 侧要动四个地方
官方写明要改生成出来的 Android 工程里的四个文件。
android/app/build.gradle(或 .kts)的 android 块里打开 Compose:buildFeatures { compose = true }(Groovy 写成 compose true),并设置 composeOptions.kotlinCompilerExtensionVersion = "1.4.8"。注释指向 Compose-Kotlin 兼容表,编译报错时再按本机 Kotlin 版本改这个数字。文件根级再加一组 dependencies:示例含 androidx.core:core-ktx:1.10.1、androidx.lifecycle:lifecycle-runtime-ktx:2.6.1、androidx.activity:activity-compose,以及 platform("androidx.compose:compose-bom:2024.06.00"),再声明 ui、ui-graphics、ui-tooling-preview、material、material3。Kotlin DSL 示例里 androidTestImplementation 用的也是 2024.06.00。同一页的 Groovy 示例里,测试依赖写成了 compose-bom:2023.08.00。两处并不一致,按自己用的 Gradle 方言对照抄,编译不过再核对 BOM。
android/build.gradle 顶部加 buildscript,classpath 示例是 com.android.tools.build:gradle:8.1.1,仓库用 google() 和 mavenCentral()。注释写 Replace with the latest version。
AndroidManifest.xml 的 <application> 里登记第二块页面:
<activity
android:name=".SecondActivity"
android:exported="true"
android:theme="@style/LaunchTheme"></activity>
它和已有的 .MainActivity 并列,不要覆盖启动用的那一块。
MainActivity.kt 里让 MainActivity 继续继承 FlutterActivity,增加与 Dart 相同的 CHANNEL 字符串,在 configureFlutterEngine 里注册 MethodChannel。call.method 等于 launchActivity 时,从 call.arguments 取出消息,构造指向 SecondActivity 的 Intent,putExtra("message", ...) 后 startActivity。同一文件底部再写 SecondActivity : ComponentActivity(),onCreate 里 setContent { ... } 画 Compose 界面,用 getIntent()?.extras?.getString("message") 读 Dart 传来的字,退出按钮调用 finish()。
官方 MainActivity 示例在 configureFlutterEngine 里先调用 GeneratedPluginRegistrant.registerWith(flutterEngine),再给 MethodChannel 设置 setMethodCallHandler。when (call.method) 对上 launchActivity 之后,用 Intent(this@MainActivity, SecondActivity::class.java) 拉起第二页。call.arguments 在示例里先转成字符串再放进 extra。示例的 try/catch 较简,捕获后仍 result.success(true),工程里按自己的错误处理补全即可。SecondActivity 继承 ComponentActivity(不是 FlutterActivity),setContent 里用 Surface + Column 放标题、传来的 message,以及调用 finish() 的退出按钮——这块已经是纯 Compose,不再套 Flutter 引擎。
文档提醒:import 列表会随库版本和你实际用到的 Compose 组件变化,跟 IDE 提示走即可。Manifest 里 android:exported="true" 和 android:theme="@style/LaunchTheme" 按示例抄。主题名若和工程现有启动主题不一致,再改成项目里实际存在的 style。这些步骤只覆盖「从 Flutter 拉起一块全屏 Compose Activity」。通道对不上时先核对那三个必须一致的值:通道名、方法名、参数形状。Platform Views 那页还写过嵌原生视图时的性能取舍(主线程与 raster 线程合并等),整页 Activity 不走那条合成路径,也就不必在本篇里套用那些开关。
改完这四处之后用 flutter run 点那颗示例里的 FloatingActionButton,应能看到第二块 Compose 页和 extra 里的 message。Kotlin 编译器扩展版本对不上时,报错信息通常会提示本机已安装的组合,再回兼容表改 kotlinCompilerExtensionVersion。
你们是整页原生设置要用这条路径,还是其实只想在 Flutter 页面里嵌一小块 Android 控件?