|
| 1 | +--- |
| 2 | +title: Launching a Jetpack Compose activity from your Flutter application |
| 3 | +short-title: Native Android activities |
| 4 | +description: Learn how to launch native Android activities in your Flutter app. |
| 5 | +--- |
| 6 | + |
| 7 | +<?code-excerpt path-base="platform_integration/compose_activities"?> |
| 8 | + |
| 9 | +Native Android activities allow you to launch fullscreen UIs that are entirely |
| 10 | +run by and on the Android platform. You will only write Kotlin code in those |
| 11 | +views (though they might pass messages to and receive messages from your Dart |
| 12 | +code) and you will have access to the full breadth of native Android functionality. |
| 13 | + |
| 14 | +Adding this functionality requires making several different types of changes to your |
| 15 | +Flutter app and its internal, generated Android app. On the Flutter side, you will |
| 16 | +need to create a new platform method channel and call its `invokeMethod` method. |
| 17 | +On the Android side, you will need to register a matching native `MethodChannel` |
| 18 | +to receive the signal from Dart and then launch a new activity. Recall that all |
| 19 | +Flutter apps (when running on Android) exist within an Android activity that is |
| 20 | +completely consumed by the Flutter app. Thus, as you will see in the code sample, |
| 21 | +the job of the native `MethodChannel` callback is to launch a second activity. |
| 22 | + |
| 23 | +:::note |
| 24 | +This page discusses how to launch native Android activities |
| 25 | +within a Flutter app. |
| 26 | +If you'd like to host native Android views in your Flutter app, |
| 27 | +see [Hosting native Android views][]. |
| 28 | +::: |
| 29 | + |
| 30 | +[Hosting native Android views]: /platform-integration/android/platform-views |
| 31 | + |
| 32 | +Not all Android activities use Jetpack Compose, but this tutorial assumes you |
| 33 | +want to use Compose. Compose activities require Android API 21 or higher. |
| 34 | + |
| 35 | +## On the Dart side |
| 36 | + |
| 37 | +On the Dart side, create a method channel and invoke it from a specific user |
| 38 | +interaction, like tapping a button. |
| 39 | + |
| 40 | +<?code-excerpt "lib/launch_compose_activity_example_1.dart"?> |
| 41 | +```dart |
| 42 | +import 'package:flutter/material.dart'; |
| 43 | +import 'package:flutter/services.dart'; |
| 44 | +
|
| 45 | +// SECTION 1: START COPYING HERE |
| 46 | +const platformMethodChannel = MethodChannel( |
| 47 | + // Note: You can change this string value, but it must match |
| 48 | + // the `CHANNEL` attribute in the next step |
| 49 | + 'com.example.flutter_android_activity', |
| 50 | +); |
| 51 | +// SECTION 1: END COPYING HERE |
| 52 | +
|
| 53 | +void main() { |
| 54 | + runApp(const MainApp()); |
| 55 | +} |
| 56 | +
|
| 57 | +class MainApp extends StatelessWidget { |
| 58 | + const MainApp({super.key}); |
| 59 | +
|
| 60 | + // SECTION 2: START COPYING HERE |
| 61 | + void _launchAndroidActivity() { |
| 62 | + platformMethodChannel.invokeMethod( |
| 63 | + // Note: You can change this value, but it must must match the |
| 64 | + // `call.method` value in the next section |
| 65 | + 'launchActivity', |
| 66 | +
|
| 67 | + // Note: You can pass any primitive data types you like. To pass complex |
| 68 | + // types, use pkg:pigeon to generate matching Dart and Kotlin classes |
| 69 | + // which share serialization logic. |
| 70 | + {'message': 'Hello from Flutter'}, |
| 71 | + ); |
| 72 | + } |
| 73 | + // SECTION 2: END COPYING HERE |
| 74 | +
|
| 75 | + @override |
| 76 | + Widget build(BuildContext context) { |
| 77 | + return MaterialApp( |
| 78 | + home: Scaffold( |
| 79 | + body: const Center( |
| 80 | + child: Text('Hello World!'), |
| 81 | + ), |
| 82 | + floatingActionButton: FloatingActionButton( |
| 83 | + // SECTION 3: Call `_launchAndroidActivity` somewhere |
| 84 | + onPressed: _launchAndroidActivity, |
| 85 | + // SECTION 3: End |
| 86 | +
|
| 87 | + tooltip: 'Launch Android activity', |
| 88 | + child: const Icon(Icons.launch), |
| 89 | + ), |
| 90 | + ), |
| 91 | + ); |
| 92 | + } |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +There are 3 important values that must match across your Dart and Kotlin code: |
| 97 | + |
| 98 | + 1. The channel name (in this sample, the value is |
| 99 | + "com.example.flutter_android_activity"). |
| 100 | + 2. The method name (in this sample, the value is "launchActivity"). |
| 101 | + 3. The structure of the data which Dart passes and the structure of the data |
| 102 | + which Kotlin expects to receive. In this case, it is a Map with a single |
| 103 | + "message" key. |
| 104 | + |
| 105 | + |
| 106 | +## On the Android side |
| 107 | + |
| 108 | +You must make changes to 4 files in the generated Android app to ready it for |
| 109 | +launching fresh Compose activities. |
| 110 | + |
| 111 | +The first file requiring modifications is `android/app/build.gradle`. |
| 112 | + |
| 113 | +1. Add the following to the existing `android` block: |
| 114 | + |
| 115 | +```groovy |
| 116 | +android { |
| 117 | + // Begin adding here |
| 118 | + buildFeatures { |
| 119 | + compose true |
| 120 | + } |
| 121 | + composeOptions { |
| 122 | + // https://developer.android.com/jetpack/androidx/releases/compose-kotlin |
| 123 | + kotlinCompilerExtensionVersion = "1.4.8" |
| 124 | + } |
| 125 | + // End adding here |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +Visit the |
| 130 | +[developer.android.com]({{site.android-dev}}/jetpack/androidx/releases/compose-kotlin) |
| 131 | +link in the code snippet and adjust `kotlinCompilerExtensionVersion`, |
| 132 | +as necessary. You should only need to do this if you receive errors during |
| 133 | +`flutter run` and those errors tell you which versions are installed on |
| 134 | +your machine. |
| 135 | + |
| 136 | +2. Next, add the following block at the bottom of the file, at the root level: |
| 137 | + |
| 138 | +```groovy |
| 139 | +dependencies { |
| 140 | + implementation("androidx.core:core-ktx:1.10.1") |
| 141 | + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") |
| 142 | + implementation("androidx.activity:activity-compose") |
| 143 | + implementation(platform("androidx.compose:compose-bom:2024.06.00")) |
| 144 | + implementation("androidx.compose.ui:ui") |
| 145 | + implementation("androidx.compose.ui:ui-graphics") |
| 146 | + implementation("androidx.compose.ui:ui-tooling-preview") |
| 147 | + implementation("androidx.compose.material:material") |
| 148 | + implementation("androidx.compose.material3:material3") |
| 149 | + testImplementation("junit:junit:4.13.2") |
| 150 | + androidTestImplementation("androidx.test.ext:junit:1.1.5") |
| 151 | + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") |
| 152 | + androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00")) |
| 153 | + androidTestImplementation("androidx.compose.ui:ui-test-junit4") |
| 154 | + debugImplementation("androidx.compose.ui:ui-tooling") |
| 155 | + debugImplementation("androidx.compose.ui:ui-test-manifest") |
| 156 | +} |
| 157 | +``` |
| 158 | + |
| 159 | +The second file requiring modifications is `android/build.gradle`. |
| 160 | + |
| 161 | +1. Add the following buildscript block at the top of the file: |
| 162 | + |
| 163 | +```groovy |
| 164 | +buildscript { |
| 165 | + dependencies { |
| 166 | + // Replace with the latest version |
| 167 | + classpath 'com.android.tools.build:gradle:8.1.1' |
| 168 | + } |
| 169 | + repositories { |
| 170 | + google() |
| 171 | + mavenCentral() |
| 172 | + } |
| 173 | +} |
| 174 | +``` |
| 175 | + |
| 176 | +The third file requiring modifications is |
| 177 | +` android/app/src/main/AndroidManifest.xml`. |
| 178 | + |
| 179 | +1. In the root application block, add the following `<activity>` declaration: |
| 180 | + |
| 181 | +```xml |
| 182 | +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> |
| 183 | + <application |
| 184 | + android:label="flutter_android_activity" |
| 185 | + android:name="${applicationName}" |
| 186 | + android:icon="@mipmap/ic_launcher"> |
| 187 | + |
| 188 | + // START COPYING HERE |
| 189 | + <activity android:name=".SecondActivity" android:exported="true" android:theme="@style/LaunchTheme"></activity> |
| 190 | + // END COPYING HERE |
| 191 | + |
| 192 | + <activity android:name=".MainActivity" …></activity> |
| 193 | + … |
| 194 | +</manifest> |
| 195 | +``` |
| 196 | + |
| 197 | +The fourth and final code requiring modifications is |
| 198 | +`android/app/src/main/kotlin/com/example/flutter_android_activity/MainActivity.kt`. |
| 199 | +Here you'll write Kotlin code for your desired Android functionality. |
| 200 | + |
| 201 | +1. Add the necessary imports at the top of the file: |
| 202 | + |
| 203 | +:::note |
| 204 | +Your imports might vary if library versions have changed or if you introduce |
| 205 | +different Compose classes when you write your own Kotlin code. Follow your IDE's |
| 206 | +hints for the correct imports you require. |
| 207 | +::: |
| 208 | + |
| 209 | +```kotlin |
| 210 | +package com.example.flutter_android_activity |
| 211 | + |
| 212 | +import android.content.Intent |
| 213 | +import android.os.Bundle |
| 214 | +import androidx.activity.ComponentActivity |
| 215 | +import androidx.activity.compose.setContent |
| 216 | +import androidx.compose.foundation.layout.Column |
| 217 | +import androidx.compose.foundation.layout.fillMaxSize |
| 218 | +import androidx.compose.material3.Button |
| 219 | +import androidx.compose.material3.MaterialTheme |
| 220 | +import androidx.compose.material3.Surface |
| 221 | +import androidx.compose.material3.Text |
| 222 | +import androidx.compose.ui.Modifier |
| 223 | +import androidx.core.app.ActivityCompat |
| 224 | +import io.flutter.embedding.android.FlutterActivity |
| 225 | +import io.flutter.embedding.engine.FlutterEngine |
| 226 | +import io.flutter.plugin.common.MethodCall |
| 227 | +import io.flutter.plugin.common.MethodChannel |
| 228 | +import io.flutter.plugins.GeneratedPluginRegistrant |
| 229 | +``` |
| 230 | + |
| 231 | +2. Modify the generated `MainActivity` class by adding a `CHANNEL` field and a |
| 232 | +`configureFlutterEngine` method: |
| 233 | + |
| 234 | +```kotlin |
| 235 | +class MainActivity: FlutterActivity() { |
| 236 | + |
| 237 | + // This value must match the `MethodChannel` name in your Dart code. |
| 238 | + private val CHANNEL = "com.example.flutter_android_activity" |
| 239 | + |
| 240 | + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { |
| 241 | + GeneratedPluginRegistrant.registerWith(flutterEngine) |
| 242 | + |
| 243 | + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { |
| 244 | + call: MethodCall, result: MethodChannel.Result -> |
| 245 | + when (call.method) { |
| 246 | + // Note: This must match the first parameter passed to `platformMethodChannel.invokeMethod` |
| 247 | + // in your Dart code. |
| 248 | + "launchActivity" -> { |
| 249 | + try{ |
| 250 | + val message = call.arguments // Takes an object, in this case it's a String |
| 251 | + val intent = Intent(this@MainActivity, SecondActivity::class.java) |
| 252 | + intent.putExtra("message", message.toString()) |
| 253 | + startActivity(intent) |
| 254 | + } catch (e: Exception){} |
| 255 | + result.success(true) |
| 256 | + } |
| 257 | + else -> {} |
| 258 | + } |
| 259 | + } |
| 260 | + } |
| 261 | +} |
| 262 | +``` |
| 263 | + |
| 264 | +3. Add a second `Activity` to the bottom of the file, which you referenced in |
| 265 | +the previous changes to `AndroidManifest.xml`: |
| 266 | + |
| 267 | +```kotlin |
| 268 | +class SecondActivity : ComponentActivity() { |
| 269 | + |
| 270 | + override fun onCreate(savedInstanceState: Bundle?) { |
| 271 | + super.onCreate(savedInstanceState) |
| 272 | + |
| 273 | + setContent { |
| 274 | + Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { |
| 275 | + Column { |
| 276 | + Text(text = "Second Activity") |
| 277 | + // Note: This must match the shape of the data passed from your Dart code. |
| 278 | + Text("" + getIntent()?.getExtras()?.getString("message")) |
| 279 | + Button(onClick = { finish() }) { |
| 280 | + Text("Exit") |
| 281 | + } |
| 282 | + } |
| 283 | + } |
| 284 | + } |
| 285 | + } |
| 286 | +} |
| 287 | +``` |
| 288 | + |
| 289 | +These steps show how to launch a native Android activity from a Flutter app, |
| 290 | +which can sometimes be an easy way to connect to specific Android functionality. |
0 commit comments