diff --git a/site/web/assets/images/android/jni-call-lifecycle-dark.svg b/site/web/assets/images/android/jni-call-lifecycle-dark.svg new file mode 100644 index 00000000000..f21306c8840 --- /dev/null +++ b/site/web/assets/images/android/jni-call-lifecycle-dark.svg @@ -0,0 +1,4 @@ + + +Android APIsFlutter AppBindingsC/C++NDK - Native Development KitJava Native InterfaceLifecycle of a Java Native Interface(JNI) Call \ No newline at end of file diff --git a/site/web/assets/images/android/jni-call-lifecycle-light.png b/site/web/assets/images/android/jni-call-lifecycle-light.png new file mode 100644 index 00000000000..38cbb9992b2 Binary files /dev/null and b/site/web/assets/images/android/jni-call-lifecycle-light.png differ diff --git a/site/web/assets/images/android/jni-call-lifecycle-light.svg b/site/web/assets/images/android/jni-call-lifecycle-light.svg new file mode 100644 index 00000000000..b10c537f6ec --- /dev/null +++ b/site/web/assets/images/android/jni-call-lifecycle-light.svg @@ -0,0 +1,4 @@ + + +Android APIsFlutter AppBindingsC/C++NDK - Native Development KitJava Native InterfaceLifecycle of a Java Native Interface(JNI) Call \ No newline at end of file diff --git a/src/content/platform-integration/android/call-jetpack-apis.md b/src/content/platform-integration/android/call-native-apis.md similarity index 97% rename from src/content/platform-integration/android/call-jetpack-apis.md rename to src/content/platform-integration/android/call-native-apis.md index d2476b9145d..d29f800ed37 100644 --- a/src/content/platform-integration/android/call-jetpack-apis.md +++ b/src/content/platform-integration/android/call-native-apis.md @@ -1,6 +1,6 @@ --- -title: "Calling JetPack APIs" -description: "Use the latest Android APIs from your Dart code" +title: "Calling Native APIs" +description: "Use an Android API directly from Flutter code" --- diff --git a/src/content/platform-integration/android/compose-activity.md b/src/content/platform-integration/android/compose-activity.md index 30e84b6674f..891fdc7006a 100644 --- a/src/content/platform-integration/android/compose-activity.md +++ b/src/content/platform-integration/android/compose-activity.md @@ -15,8 +15,27 @@ you will have access to the full breadth of native Android functionality. Adding this functionality requires making several changes to your Flutter app and its internal, generated Android app. + +There are two ways to do it: Either using `jnigen` or Method Channels. + +## Overview of launching Activities using `jnigen` + +On the Flutter side, you will need to create Dart bindings for various Android APIs +and call them using Dart code. This will involve setting up `jni`, `jnigen`, +and a configuration file describing the generated output. + +On the Android side, you will need to make some modifications to some Android build +files to account for Compose views and a new `Activity`. + +The [code][] for this version is available on Github. + +[code]:https://github.com/flutter/demos/tree/main/native_interop_demos/android_launch_activity + +## Overview of launching Activities using Method Channels + On the Flutter side, you will need to create a new platform method channel and call its `invokeMethod` method. + On the Android side, you will need to register a matching native `MethodChannel` to receive the signal from Dart and then launch a new activity. Recall that all Flutter apps (when running on Android) exist within @@ -36,7 +55,360 @@ check out [Hosting native Android views][]. Not all Android activities use Jetpack Compose, but this tutorial assumes you want to use Compose. -## On the Dart side +## Launch Activity Using Native Interop (`jnigen`) + +### On the command line + +On the commandline, add `jnigen` as a development dependency +and `jni` as a runtime dependency. `jnigen` will be used to +generate Dart bindings and then when the app is run, `jni` +will execute them. +```sh +flutter pub add jnigen --dev +flutter pub add jni +``` + +### On the Dart side + +In your dart code, create jnigen.dart file specifying the +bindings you will be generating. + +First, run the following command to build the app and make +available the files needed to execute code generation. + +```sh +flutter build apk +``` + + +In a new file `tool/jnigen.dart`, add the following code. + +```dart +import 'dart:io'; + +import 'package:jnigen/jnigen.dart'; + +void main(List args) { + final packageRoot = Platform.script.resolve('../'); + generateJniBindings( + Config( + outputConfig: OutputConfig( + dartConfig: DartCodeOutputConfig( + path: packageRoot.resolve('lib/gen/android.g.dart'), + structure: OutputStructure.singleFile, + ), + ), + androidSdkConfig: AndroidSdkConfig(addGradleDeps: true), + classes: [ + // provided by Android OS + 'android.os.Bundle', + 'android.content.Intent', + 'android.content.Context' + ], + ), + ); +} +``` + +Execute `dart run tool/jnigen.dart` to generate the Dart bindings. + +With the bindings generated, we can directly + +```dart +import 'package:flutter/material.dart'; +// uses added namespace because some bindings conflict with Dart classes (Intent) +import 'package:android_launch_activity/gen/android.g.dart' as native; +import 'package:jni/jni.dart'; + +// Context.fromReference ensures we get Android Context object +// rather than the default `JObject` +var context = native.Context.fromReference(Jni.androidApplicationContext.reference); + +void main() { + runApp(const MainApp()); +} + +class MainApp extends StatelessWidget { + const MainApp({super.key}); + + // SECTION 2: START COPYING HERE + void _launchAndroidActivity() { + + var intent = native.Intent.new$1(context, native.SecondActivity.type.jClass); + intent.addFlags(native.Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra$18('message'.toJString(), 'Hello from Flutter'.toJString()); + context.startActivity(intent); + + intent.release(); + +} + // 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), + ), + ), + ); + } +} + +``` + + +### On the Android Side + +You must make changes to 4 files in the generated Android app to +ready it for launching fresh Compose activities. + +The first file requiring modifications is `android/app/build.gradle`. + + 1. Add the following to the existing `android` block: + + + + + ```kotlin title="android/app/build.gradle.kts" + android { + // Begin adding here + buildFeatures { + compose = true + } + composeOptions { + // https://developer.android.com/jetpack/androidx/releases/compose-kotlin + kotlinCompilerExtensionVersion = "1.4.8" + } + // End adding here + } + ``` + + + + + ```groovy title="android/app/build.gradle" + android { + // Begin adding here + buildFeatures { + compose true + } + composeOptions { + // https://developer.android.com/jetpack/androidx/releases/compose-kotlin + kotlinCompilerExtensionVersion = "1.4.8" + } + // End adding here + } + ``` + + + + + Visit the [developer.android.com][] link in the code snippet and + adjust `kotlinCompilerExtensionVersion`, as necessary. + You should only need to do this if you + receive errors during `flutter run` and those errors tell you + which versions are installed on your machine. + + [developer.android.com]: {{site.android-dev}}/jetpack/androidx/releases/compose-kotlin + + 2. Next, add the following block at the bottom of the file, at the root level: + + + + + ```kotlin title="android/app/build.gradle.kts" + 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") + } + ``` + + + + + ```groovy title="android/app/build.gradle" + 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" + } + ``` + + + + + The second file requiring modifications is `android/build.gradle`. + + 1. Add the following buildscript block at the top of the file: + + + + + ```kotlin title="android/build.gradle.kts" + buildscript { + dependencies { + // Replace with the latest version. + classpath("com.android.tools.build:gradle:8.1.1") + } + repositories { + google() + mavenCentral() + } + } + ``` + + + + + ```groovy title="android/build.gradle" + buildscript { + dependencies { + // Replace with the latest version. + classpath 'com.android.tools.build:gradle:8.1.1' + } + repositories { + google() + mavenCentral() + } + } + ``` + + + + + The third file requiring modifications is + `android/app/src/main/AndroidManifest.xml`. + + 1. In the root application block, add the following `` declaration: + + ```xml title="android/app/src/main/AndroidManifest.xml" + + + + // START COPYING HERE + + // END COPYING HERE + + + … + + ``` + + The fourth and final code requiring modifications is + `android/app/src/main/kotlin/com/example/flutter_android_activity/MainActivity.kt`. + Here you'll write Kotlin code for your desired Android functionality. + + 1. Add the necessary imports at the top of the file + :::note + Your imports might vary if library versions have changed or + if you introduce different Compose classes when + you write your own Kotlin code. + Follow your IDE's hints for the correct imports you require. + ::: + +```kotlin + package com.example.android_launch_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 + ``` + + + 1. Add a second `Activity` to the bottom of the file, which you + referenced in the previous changes to `AndroidManifest.xml`: + + ```kotlin title="MainActivity.kt" +class SecondActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + MaterialTheme { + 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") + } + } + } + } + } + } +} + ``` + + +## Launch Activity using `MethodChannels` + +### On the Dart side On the Dart side, create a method channel and invoke it from a specific user interaction, like tapping a button. @@ -105,7 +477,7 @@ There are 3 important values that must match across your Dart and Kotlin code: In this case, the data is a map with a single `"message"` key. -## On the Android side +### On the Android side You must make changes to 4 files in the generated Android app to ready it for launching fresh Compose activities. diff --git a/src/content/platform-integration/android/jnigen.md b/src/content/platform-integration/android/jnigen.md new file mode 100644 index 00000000000..1e08e4addce --- /dev/null +++ b/src/content/platform-integration/android/jnigen.md @@ -0,0 +1,240 @@ +--- +title: "Calling Native APIs with jnigen" +description: "Use an Android API directly from Flutter code" +--- + +## Overview + +`jnigen` stands for Java Native Interface GENeration. It is a means for +calling native Java and Kotlin (JVM) code from Dart. It builds upon the FFI (Foreign Function Interface) package that C-based languages use. + +## How It Works + +Before we can use a native interop call in an application, we need to do a little setup. + +The APISummarizer is a tool that reads source and bytecode to determine which classes and functions are present. The abstract syntax tree defining the desired classes is read to create Dart versions with modifications to account for the differences between the Java platform and Dart. + + +Node tree + + + +:::warning +There is an alternate method of specifying generation output using a `jnigen.yaml` file. +That method is considered deprecated. +::: + + +### Step 1: Create a new app project + +### Step 2: Add `jni` and `jnigen` as dependencies + +```sh +dart pub add jni +dart pub add jnigen --dev +``` + +To make sure that Android OS APIs and whatever dependencies are available to `jnigen`, run the following to build +an Android apk of the application. + +```sh +flutter build apk +``` + +### Step 3: Set configuration of Dart bindings +There is a Dart API in `jnigen` to specify the properties your generated should have. You will create +a Dart file. `tool/jnigen.dart` is the convention but you can create it anywhere. The file needs at +minimum a single function call to `generateJniBindings` that accepts a `Config` object as a parameter. + +Here is a minimal configuration file. + +```dart +import 'dart:io'; + +import 'package:jnigen/jnigen.dart'; + +void main(List args) { + final packageRoot = Platform.script.resolve('../'); + generateJniBindings( + Config( + outputConfig: OutputConfig( + dartConfig: DartCodeOutputConfig( + path: packageRoot.resolve('lib/gen/'), + structure: OutputStructure.packageStructure, + ), + ), + classes: [ + // Core Java classes are generally available + // like java.util.ArrayList, etc + + // Android OS classes are also available + // provided `flutter build apk` is run + // before this function + + // 'java.util.ArrayList', + // 'android.widget.Toast', + ], + ), + ); + + +``` + +The `Config` object is a Dart API for specifying: + +* the location of developer-created source code, +* the final location of the generated Dart code, +* dependencies to download from online repositories, and, +* specifics of the Android version to build against. + +```dart +Config({ + required OutputConfig outputConfig, + required List classes, + Set? experiments, + List? sourcePath, + List? classPath, + String? preamble, + Map? customClassBody, + AndroidSdkConfig? androidSdkConfig, + MavenDownloads? mavenDownloads, + SummarizerOptions? summarizerOptions, + List? nonNullAnnotations, + List? nullableAnnotations, + Level logLevel = Level.INFO, + String? dumpJsonTo, + List? imports, + List? visitors +}) +``` + +Here is the final configuration file to generate Dart bindings to show an Android `Toast` message. + +```dart +import 'dart:io'; + +import 'package:jnigen/jnigen.dart'; + +void main(List args) { + final packageRoot = Platform.script.resolve('../'); + generateJniBindings( + Config( + outputConfig: OutputConfig( + dartConfig: DartCodeOutputConfig( + path: packageRoot.resolve('lib/gen/android.g.dart'), + structure: OutputStructure.singleFile, + ), + ), + androidSdkConfig: AndroidSdkConfig(addGradleDeps: true), + classes: [ + 'android.widget.Toast', // provided by Android OS + ], + ), + ); +} + +``` + +Finally, run + +```sh +dart tool/jnigen.dart +``` + + +### Step 4: Call Dart bindings + +```dart +// Retrieves an Android context to pass with native calls +JObject context = Jni.androidApplicationContext; + +/// Display DateTime retrieved from Dart +void showToast() { + final message = 'The time is now ${DateTime.now()}'; + +// Corresponds to this second signature +// public static Toast makeText (Context context, +// CharSequence text, +// int duration) +// First one uses R namespace resources + Toast.makeText$1(context, message.toJString(), Toast.LENGTH_LONG)!.show(); +} +``` + +Here is the full `main.dart` file. + +:::note +Java/Kotlin and Dart differ on support of overloaded functions, that's to say functions that use the same +name but differ on return type, parameter type, or number of parameters. + +To compensate for this, when Dart bindings are built, overloaded functions take the form of +`functionName$`. At present, they are parsed in the order that they appear in the files +so there might not be a correlation between number and type of parameters and the eventual Dart identifier. +::: + +```dart + +import 'package:android_toast_demo/gen/android/os/_package.dart'; +import 'package:android_toast_demo/gen/android/widget/_package.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:jni/jni.dart'; + + +JObject context = Jni.androidApplicationContext; + + +/// Display DateTime retrieved from Dart +void showToast() { + final message = 'The time is now ${DateTime.now()}'; + +// Corresponds to this second signature +// public static Toast makeText (Context context, +// CharSequence text, +// int duration) +// First one uses R namespace resources + Toast.makeText$1(context, message.toJString(), Toast.LENGTH_LONG)!.show(); +} + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData(primarySwatch: Colors.teal), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatelessWidget { + const MyHomePage({super.key, required this.title}); + + final String title; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(title)), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + child: const Text('Show Time'), + onPressed: () => showToast(), + ), + ], + ), + ), + ); + } +} + +``` diff --git a/src/content/platform-integration/android/request-permission.md b/src/content/platform-integration/android/request-permission.md new file mode 100644 index 00000000000..02c36f574fe --- /dev/null +++ b/src/content/platform-integration/android/request-permission.md @@ -0,0 +1,195 @@ +--- +title: "Request Android permissions" +description: "Request and manage Android permissions from jni code using a plugin" +--- + +## Overview + +This page details a means to use `jnigen` to wrap native Android code and package that functionality +as a plugin. It demonstrates requesting and querying Android permissions natively from Flutter. + +With this flow, one only has to edit a single file on the Android side. + +The code for [this plugin][] can be found here. + +## How It Works + +### `jnigen` Setup + +```dart + +import 'package:jnigen/jnigen.dart'; + +void main(List args) { + final packageRoot = Platform.script.resolve('../'); + generateJniBindings( + Config( + outputConfig: OutputConfig( + dartConfig: DartCodeOutputConfig( + path: packageRoot.resolve('lib/gen/android.g.dart'), + structure: OutputStructure.singleFile, + ), + ), + androidSdkConfig: AndroidSdkConfig(addGradleDeps: true, androidExample: 'example/'), + classes: [ + // provided by Android OS + 'android.app.Application', + 'androidx.activity.ComponentActivity', + 'androidx.fragment.app.FragmentActivity', + 'androidx.activity.result.ActivityResult', + 'androidx.core.app.ActivityCompat', + 'androidx.activity.result.ActivityResultCallback', + 'androidx.activity.result.ActivityResultLauncher', + 'androidx.activity.result.contract.ActivityResultContract', + 'android.content.Intent', + //'android.content.Context', + 'androidx.core.content.ContextCompat', + 'android.Manifest', + 'android.content.pm.PackageManager' + ], + ), + ); +} + +``` + +### Plugin implementation + +```dart +import 'package:flutter/foundation.dart'; + +import 'permissions_plugin_platform_interface.dart'; +import 'gen/android.g.dart'; +import 'package:jni/jni.dart'; + +class PermissionsPlugin { + Future getPlatformVersion() { + return PermissionsPluginPlatform.instance.getPlatformVersion(); + } + + // + bool checkPermission(JObject context, String permission) { + // Returns a simple true or false if the permission has been granted + var result = ContextCompat.checkSelfPermission( + context, + permission.toJString(), + ); + return result == PackageManager.PERMISSION_GRANTED ? true : false; + } + + int checkAndRequestPermission( + JObject context, + String permission, + Function callback, + ) { + // Do I have permission? + if (ContextCompat.checkSelfPermission(context, permission.toJString()) == + PackageManager.PERMISSION_GRANTED) { + callback(); + } else if (ActivityCompat.shouldShowRequestPermissionRationale( + Jni.androidActivity(PlatformDispatcher.instance.engineId!), + permission.toJString(), + ) == + true) { + // Has the user denied the permission before? + // Give a reason why I need the permission + // and allow a re-request + print("I should ask for permission"); + // TODO Flow to show UI to reshow perms dialog + return -2; + } else { + // Ask for permission + ActivityCompat.requestPermissions( + Jni.androidActivity(PlatformDispatcher.instance.engineId!), + JArray.of(JString.type, [permission.toJString()]), + 0, + ); + } + return 0; + } +} + +``` + +### Using the plugin in an app + +Using this implementation of permissions means you only need to edit one bit of Android code to +add the possible permissions into the app's `AndroidManifest.xml` file. If the permission +does not exist in that file, `checkAndRequestPermission` will fail silently. + +```xml + + + + + + + +``` + +#### Initialize the plugin + +```dart +class _MyAppState extends State { + String _platformVersion = 'Unknown'; + final _permissionsPlugin = PermissionsPlugin(); + + @override + void initState() { + super.initState(); + initPlatformState(); + } + // ... +} +``` + +Here is how the two implemented functions look on the Dart side. + +```dart +// Returns true or false if the permission has been granted +_permissionsPlugin.checkPermission( + Jni.androidApplicationContext, + "android.permission.CAMERA" +); + + +// Check for the permission and run execute a callback if allowed +_permissionsPlugin.checkAndRequestPermission( + Jni.androidApplicationContext, + "android.permission.CAMERA", + () { /* Code to run if the permission was granted */}, + ); + +``` + + + +```dart +@override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('Plugin example app')), + body: Center( + child: Column( + children: [ + Text('Running on: $_platformVersion\n'), + FilledButton( + child: Text("Request Camera Permissions"), + onPressed: () { +_permissionsPlugin.checkAndRequestPermission( + Jni.androidApplicationContext, + "android.permission.CAMERA", + () {}, + ); + }, + ), + ], + ), + ), + ), + ); + +``` + +[this plugin]: https://github.com/flutter/demos/tree/main/native_interop_demos/permissions_plugin diff --git a/src/content/platform-integration/native-code-options.md b/src/content/platform-integration/native-code-options.md new file mode 100644 index 00000000000..78a1dff4850 --- /dev/null +++ b/src/content/platform-integration/native-code-options.md @@ -0,0 +1,56 @@ +--- +title: Writing custom platform-specific code +shortTitle: Platform-specific code +description: Learn how about the options to call platform specific code in your app. +--- + +## Overview + +You have a number of options to use platform-specific code +in your Dart and Flutter apps from low level options that +require a deep knowledge of Dart and the host language to +high level options that allow you to use a high level API +to abstract some of the native bits away. They are: + +* Direct native interop using FFI or JNI + +This method involves an additional step to generate code +bindings from the host platform language into Dart. + +* Self-managed platform channels + +This method involves using a plugin that hosts native code +which is executed asynchronously through message passing +from Dart to iOS/Android and back. + +* Type-safe platform channels via Pigeon + +With regular method channels, one must manage type safety +and object serialization on their own. With Pigeon, the +message contents are managed as well as giving the developer +a level of control over the generated Dart API. It is best +seen as a superset of regular platform channels. + +* Or some combination of the above. + + +## Which should I use? + +There are a lot of considerations that may determine which to +use including familiarity with the language the native OS is +written in, comfort with low-level considerations like memory +management, and the breadth of the underlying API surface you +would like to implement. + +*I need to access a few native-code functions.* + +Use `ffi/jnigen`. Making a discrete plugin for a single function +on a single class would be overkill. + +*I need to implement the same interface on iOS and Android.* + +Use `pigeon`. + +*I need to re-implement a full native API in Dart.* + +Consider `pigeon` augmented by `ffi/jnigen`. diff --git a/src/content/platform-integration/platform-channels.md b/src/content/platform-integration/platform-channels.md index 67e27eac9c7..5c0ee38089c 100644 --- a/src/content/platform-integration/platform-channels.md +++ b/src/content/platform-integration/platform-channels.md @@ -1,7 +1,7 @@ --- -title: Writing custom platform-specific code -shortTitle: Platform-specific code -description: Learn how to write custom platform-specific code in your app. +title: Using message passing to execute platform-specific code +shortTitle: Platform-channel code +description: Learn how to use platform channels in your app. --- diff --git a/src/data/sidenav/default.yml b/src/data/sidenav/default.yml index 9dfc17b4c68..89795661f15 100644 --- a/src/data/sidenav/default.yml +++ b/src/data/sidenav/default.yml @@ -365,9 +365,11 @@ permalink: /reference/supported-platforms - title: Build desktop apps with Flutter permalink: /platform-integration/desktop - - title: Write platform-specific code + - title: Write platform-specific code (new) + permalink: /platform-integration/native-code-options + - title: Communicate with the host OS (old pigeon/platform channels topic) permalink: /platform-integration/platform-channels - - title: Bind to native code + - title: Bind to native code using ffi (title change) permalink: /platform-integration/bind-native-code - title: Android permalink: /platform-integration/android @@ -380,10 +382,14 @@ permalink: /platform-integration/android/predictive-back - title: Host a native Android view permalink: /platform-integration/android/platform-views - - title: Calling JetPack APIs - permalink: /platform-integration/android/call-jetpack-apis - - title: Launch a Jetpack Compose activity + - title: Call native APIs (delete) + permalink: /platform-integration/android/call-native-apis + - title: Use jnigen to call Android code (new) + permalink: /platform-integration/android/jnigen + - title: Launch a Jetpack Compose activity (updated) permalink: /platform-integration/android/compose-activity + - title: Request Android permissions from Flutter (new) + permalink: /platform-integration/android/request-permission - title: Restore state on Android permalink: /platform-integration/android/restore-state-android - title: Target ChromeOS with Android @@ -910,4 +916,4 @@ - title: flutter CLI permalink: /reference/flutter-cli - title: API docs - permalink: https://api.flutter.dev \ No newline at end of file + permalink: https://api.flutter.dev