Skip to main content

从 Flutter 应用启动 Jetpack Compose 活动

原生 Android 活动允许您启动完全由 Android 平台运行并在其上运行的全屏 UI。您只需编写这些视图中的 Kotlin 代码(尽管它们可能会向您的 Dart 代码传递消息并接收来自您的 Dart 代码的消息),并且您可以访问原生 Android 功能的全部内容。

添加此功能需要对您的 Flutter 应用及其内部生成的 Android 应用进行多项更改。在 Flutter 端,您需要创建一个新的平台方法通道并调用其 invokeMethod 方法。在 Android 端,您需要注册一个匹配的原生 MethodChannel 来接收来自 Dart 的信号,然后启动一个新的活动。请记住,所有 Flutter 应用(在 Android 上运行时)都存在于一个完全由 Flutter 应用使用的 Android 活动中。因此,正如您将在代码示例中看到的,原生 MethodChannel 回调的工作是启动第二个活动。

并非所有 Android 活动都使用 Jetpack Compose,但本教程假设您想使用 Compose。

Dart 端

#

在 Dart 端,创建一个方法通道并从特定的用户交互(例如点击按钮)调用它。

dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

// SECTION 1: START COPYING HERE
const platformMethodChannel = MethodChannel(
  // 注意:您可以更改此字符串值,但它必须与下一步中的 `CHANNEL` 属性匹配。
  'com.example.flutter_android_activity',
);
// SECTION 1: END COPYING HERE

void main() {
  runApp(const MainApp());
}

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  // SECTION 2: START COPYING HERE
  void _launchAndroidActivity() {
    platformMethodChannel.invokeMethod(
      // 注意:您可以更改此值,但它必须与下一部分中的 `call.method` 值匹配。
      'launchActivity',

      // 注意:您可以传递任何您喜欢的基本数据类型。
      // 要传递复杂类型,请使用 package:pigeon 生成共享序列化逻辑的匹配 Dart 和 Kotlin 类。
      {'message': 'Hello from Flutter'},
    );
  }
  // SECTION 2: END COPYING HERE

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: const Center(
          child: Text('Hello World!'),
        ),
        floatingActionButton: FloatingActionButton(
          // SECTION 3: 在某处调用 `_launchAndroidActivity`。
          onPressed: _launchAndroidActivity,
          // SECTION 3: End

          tooltip: '启动 Android 活动',
          child: const Icon(Icons.launch),
        ),
      ),
    );
  }
}

Dart 和 Kotlin 代码中必须匹配 3 个重要值:

  1. 通道名称(在此示例中,值为 "com.example.flutter_android_activity")。
  2. 方法名称(在此示例中,值为 "launchActivity")。
  3. Dart 传递的数据结构和 Kotlin 期望接收的数据结构。 在本例中,数据是一个具有单个 "message" 键的映射。

Android 端

#

您必须修改生成的 Android 应用中的 4 个文件才能准备好启动新的 Compose 活动。

第一个需要修改的文件是 android/app/build.gradle

  1. 将以下内容添加到现有的 android 块中:

    android/app/build.gradle
    groovy
    android {
      // 开始添加
      buildFeatures {
        compose true
      }
      composeOptions {
        // https://developer.android.com/jetpack/androidx/releases/compose-kotlin
        kotlinCompilerExtensionVersion = "1.4.8"
      }
      // 结束添加
    }

    访问代码片段中的 developer.android.com 链接并根据需要调整 kotlinCompilerExtensionVersion。只有当您在 flutter run 期间收到错误并且这些错误告诉您机器上安装了哪些版本时,您才需要这样做。

  2. 接下来,在文件的底部,根级别添加以下块:

    android/app/build.gradle
    groovy
    dependencies {
        implementation("androidx.core:core-ktx:1.10.1")
        implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
        implementation("androidx.activity:activity-compose")
        implementation(platform("androidx.compose:compose-bom:2024.06.00"))
        implementation("androidx.compose.ui:ui")
        implementation("androidx.compose.ui:ui-graphics")
        implementation("androidx.compose.ui:ui-tooling-preview")
        implementation("androidx.compose.material:material")
        implementation("androidx.compose.material3:material3")
        testImplementation("junit:junit:4.13.2")
        androidTestImplementation("androidx.test.ext:junit:1.1.5")
        androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
        androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00"))
        androidTestImplementation("androidx.compose.ui:ui-test-junit4")
        debugImplementation("androidx.compose.ui:ui-tooling")
        debugImplementation("androidx.compose.ui:ui-test-manifest")
    }

    第二个需要修改的文件是 android/build.gradle

  3. 在文件的顶部添加以下 buildscript 块:

    android/build.gradle
    groovy
    buildscript {
        dependencies {
            // 使用最新版本替换。
            classpath 'com.android.tools.build:gradle:8.1.1'
        }
        repositories {
            google()
            mavenCentral()
        }
    }

    第三个需要修改的文件是 android/app/src/main/AndroidManifest.xml

  4. 在根应用程序块中,添加以下 <activity> 声明:

    android/app/src/main/AndroidManifest.xml
    xml
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        <application
            android:label="flutter_android_activity"
            android:name="${applicationName}"
            android:icon="@mipmap/ic_launcher">
    
           // START COPYING HERE
            <activity android:name=".SecondActivity" android:exported="true" android:theme="@style/LaunchTheme"></activity>
           // END COPYING HERE
    
           <activity android:name=".MainActivity" …></activity>
    
    </manifest>

    第四个也是最后一个需要修改的代码是 android/app/src/main/kotlin/com/example/flutter_android_activity/MainActivity.kt。在这里,您将为所需的 Android 功能编写 Kotlin 代码。

  5. 在文件的顶部添加必要的导入:

    MainActivity.kt
    kotlin
    package com.example.flutter_android_activity
    
    import android.content.Intent
    import android.os.Bundle
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.material3.Button
    import androidx.compose.material3.MaterialTheme
    import androidx.compose.material3.Surface
    import androidx.compose.material3.Text
    import androidx.compose.ui.Modifier
    import androidx.core.app.ActivityCompat
    import io.flutter.embedding.android.FlutterActivity
    import io.flutter.embedding.engine.FlutterEngine
    import io.flutter.plugin.common.MethodCall
    import io.flutter.plugin.common.MethodChannel
    import io.flutter.plugins.GeneratedPluginRegistrant
  6. 通过添加 CHANNEL 字段和 configureFlutterEngine 方法来修改生成的 MainActivity 类:

    MainActivity.kt
    kotlin
    class MainActivity: FlutterActivity() {
        // 此值必须与 Dart 代码中的 `MethodChannel` 名称匹配。
        private val CHANNEL = "com.example.flutter_android_activity"
    
        override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
            GeneratedPluginRegistrant.registerWith(flutterEngine)
    
            MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
                call: MethodCall, result: MethodChannel.Result ->
                    when (call.method) {
                        // 注意:这必须与 Dart 代码中传递给 `platformMethodChannel.invokeMethod` 的第一个参数匹配。
                        "launchActivity" -> {
                            try {
                                // 获取一个对象,在本例中是一个字符串。
                                val message = call.arguments
                                val intent = Intent(this@MainActivity, SecondActivity::class.java)
                                intent.putExtra("message", message.toString())
                                startActivity(intent)
                            } catch (e: Exception){}
                                result.success(true)
                            }
                            else -> {}
                    }
            }
        }
    }
  7. 在文件的底部添加第二个 Activity,您在前面对 AndroidManifest.xml 的更改中已经引用过它:

    MainActivity.kt
    kotlin
    class SecondActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            setContent {
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                    Column {
                        Text(text = "Second Activity")
                        // 注意:这必须与从 Dart 代码传递的数据的形状匹配。
                        Text("" + getIntent()?.getExtras()?.getString("message"))
                        Button(onClick = {  finish() }) {
                            Text("Exit")
                        }
                    }
                }
            }
        }
    }

这些步骤展示了如何从 Flutter 应用启动原生 Android 活动,这有时可能是连接到特定 Android 功能的简单方法。