Skip to content

Commit e565f7e

Browse files
Adds "Launching a Compose activity" article (#11219)
## Presubmit checklist - [ ] This PR is marked as draft with an explanation if not meant to land until a future stable release. - [x] This PR doesn’t contain automatically generated corrections (Grammarly or similar). - [x] This PR follows the [Google Developer Documentation Style Guidelines](https://developers.google.com/style) — for example, it doesn’t use _i.e._ or _e.g._, and it avoids _I_ and _we_ (first person). - [x] This PR uses [semantic line breaks](https://github.com/dart-lang/site-shared/blob/main/doc/writing-for-dart-and-flutter-websites.md#semantic-line-breaks) of 80 characters or fewer. --------- Co-authored-by: Shams Zakhour (ignore Sfshaza) <[email protected]>
1 parent be099d6 commit e565f7e

File tree

3 files changed

+362
-0
lines changed

3 files changed

+362
-0
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter/services.dart';
3+
4+
// SECTION 1: START COPYING HERE
5+
const platformMethodChannel = MethodChannel(
6+
// Note: You can change this string value, but it must match
7+
// the `CHANNEL` attribute in the next step
8+
'com.example.flutter_android_activity',
9+
);
10+
// SECTION 1: END COPYING HERE
11+
12+
void main() {
13+
runApp(const MainApp());
14+
}
15+
16+
class MainApp extends StatelessWidget {
17+
const MainApp({super.key});
18+
19+
// SECTION 2: START COPYING HERE
20+
void _launchAndroidActivity() {
21+
platformMethodChannel.invokeMethod(
22+
// Note: You can change this value, but it must must match the
23+
// `call.method` value in the next section
24+
'launchActivity',
25+
26+
// Note: You can pass any primitive data types you like. To pass complex
27+
// types, use pkg:pigeon to generate matching Dart and Kotlin classes
28+
// which share serialization logic.
29+
{'message': 'Hello from Flutter'},
30+
);
31+
}
32+
// SECTION 2: END COPYING HERE
33+
34+
@override
35+
Widget build(BuildContext context) {
36+
return MaterialApp(
37+
home: Scaffold(
38+
body: const Center(
39+
child: Text('Hello World!'),
40+
),
41+
floatingActionButton: FloatingActionButton(
42+
// SECTION 3: Call `_launchAndroidActivity` somewhere
43+
onPressed: _launchAndroidActivity,
44+
// SECTION 3: End
45+
46+
tooltip: 'Launch Android activity',
47+
child: const Icon(Icons.launch),
48+
),
49+
),
50+
);
51+
}
52+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: flutter_android_activity
2+
description: Examples showcasing Flutter's support for launching native Android activities.
3+
4+
publish_to: none
5+
6+
version: 1.0.0+1
7+
8+
environment:
9+
sdk: ^3.5.0
10+
11+
dependencies:
12+
flutter:
13+
sdk: flutter
14+
15+
dev_dependencies:
16+
flutter_test:
17+
sdk: flutter
18+
19+
flutter:
20+
uses-material-design: true
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
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

Comments
 (0)