跳到主內容

從 Flutter 應用程式啟動 Jetpack Compose Activity

瞭解如何在您的 Flutter 應用程式中啟動原生 Android Activity。

原生 Android Activity 允許您啟動完全由 Android 平臺執行和存在的全屏 UI。您只需在這些檢視中編寫 Kotlin 程式碼(儘管它們可能向 Dart 程式碼傳送訊息並接收訊息),並且您將可以訪問完整的原生 Android 功能。

新增此功能需要在您的 Flutter 應用程式及其內部生成的 Android 應用程式中進行一些更改。在 Flutter 端,您需要建立一個新的平臺方法通道並呼叫其 invokeMethod 方法。在 Android 端,您需要註冊一個匹配的原生 MethodChannel 來接收來自 Dart 的訊號,然後啟動一個新的 Activity。請記住,所有 Flutter 應用程式(在 Android 上執行時)都存在於一個完全被 Flutter 應用程式消耗的 Android Activity 中。因此,如您在程式碼示例中所見,原生 MethodChannel 回撥的工作是啟動第二個 Activity。

並非所有 Android Activity 都使用 Jetpack Compose,但本教程假定您希望使用 Compose。

在 Dart 側

#

在 Dart 端,建立一個方法通道並從特定的使用者互動(例如點選按鈕)中呼叫它。

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

// SECTION 1: START COPYING HERE
const platformMethodChannel = MethodChannel(
  // Note: You can change this string value, but it must match
  // the `CHANNEL` attribute in the next step.
  '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(
      // Note: You can change this value, but it must match
      // the `call.method` value in the next section.
      'launchActivity',

      // Note: You can pass any primitive data types you like.
      // To pass complex types, use package:pigeon to generate
      // matching Dart and Kotlin classes that share serialization logic.
      {'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: Call `_launchAndroidActivity` somewhere.
          onPressed: _launchAndroidActivity,

          // SECTION 3: End
          tooltip: 'Launch Android activity',
          child: const Icon(Icons.launch),
        ),
      ),
    );
  }
}

Dart 和 Kotlin 程式碼中必須匹配 3 個重要值

  1. 通道名稱(在本示例中,值為 "com.example.flutter_android_activity")。
  2. 方法名稱(在本示例中,值為 "launchActivity")。
  3. Dart 傳遞的資料結構以及 Kotlin 期望接收的資料結構。在這種情況下,資料是一個包含單個 "message" 鍵的 map。

在 Android 端

#

您必須修改生成的 Android 應用程式中的 4 個檔案,才能使其準備好啟動新的 Compose Activity。

需要修改的第一個檔案是 android/app/build.gradle

  1. 將以下內容新增到現有的 android 塊中

    android/app/build.gradle.kts
    kotlin
    android {
      // Begin adding here
      buildFeatures {
        compose = true
      }
      composeOptions {
        // https://developer.android.com/jetpack/androidx/releases/compose-kotlin
        kotlinCompilerExtensionVersion = "1.4.8"
      }
      // End adding here
    }
    
    android/app/build.gradle
    groovy
    android {
      // Begin adding here
      buildFeatures {
        compose true
      }
      composeOptions {
        // https://developer.android.com/jetpack/androidx/releases/compose-kotlin
        kotlinCompilerExtensionVersion = "1.4.8"
      }
      // End adding here
    }
    

    訪問程式碼片段中的 developer.android.com 連結,並根據需要調整 kotlinCompilerExtensionVersion。只有在 flutter run 期間遇到錯誤,並且這些錯誤告訴您機器上安裝了哪些版本時,才需要執行此操作。

  2. 接下來,將以下塊新增到檔案的底部,位於根級別

    android/app/build.gradle.kts
    kotlin
    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:2024.06.00"))
        androidTestImplementation("androidx.compose.ui:ui-test-junit4")
        androidTestImplementation("androidx.compose.ui:ui-test-junit4")
        debugImplementation("androidx.compose.ui:ui-tooling")
        debugImplementation("androidx.compose.ui:ui-test-manifest")
    }
    
    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")
    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.kts
    kotlin
    buildscript {
        dependencies {
            // Replace with the latest version.
            classpath("com.android.tools.build:gradle:8.1.1")
        }
        repositories {
            google()
            mavenCentral()
        }
    }
    
    android/build.gradle
    groovy
    buildscript {
        dependencies {
            // Replace with the latest version.
            classpath 'com.android.tools.build:gradle:8.1.1'
        }
        repositories {
            google()
            mavenCentral()
        }
    }
    

    需要修改的第三個檔案是 android/app/src/main/AndroidManifest.xml

  4. 在根 application 塊中,新增以下 <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。在這裡,您將編寫 Kotlin 程式碼來實現您期望的 Android 功能。

  5. 在檔案的頂部新增必要的 import

    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() {
        // This value must match the `MethodChannel` name in your Dart code.
        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) {
                        // Note: This must match the first parameter passed to
                        // `platformMethodChannel.invokeMethod` in your Dart code.
                        "launchActivity" -> {
                            try {
                                // Takes an object, in this case a String.
                                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")
                        // Note: This must match the shape of the data passed from your Dart code.
                        Text("" + getIntent()?.getExtras()?.getString("message"))
                        Button(onClick = {  finish() }) {
                            Text("Exit")
                        }
                    }
                }
            }
        }
    }
    

這些步驟展示瞭如何從 Flutter 應用程式啟動原生 Android Activity,這有時可能是連線到特定 Android 功能的一種簡單方法。