diff --git a/firebase.json b/firebase.json
index 1dd4a98080b..c343ccc4285 100644
--- a/firebase.json
+++ b/firebase.json
@@ -173,6 +173,7 @@
{ "source": "/packages-and-plugins/plugin-api-migration", "destination": "/release/breaking-changes/plugin-api-migration", "type": 301 },
{ "source": "/platform-integration/android/androidx-migration", "destination": "/release/breaking-changes/androidx-migration", "type": 301 },
{ "source": "/platform-integration/android/c-interop", "destination": "/platform-integration/legacy-ffi-plugin", "type": 301 },
+ { "source": "/platform-integration/android/call-jetpack-apis", "destination": "/platform-integration/android/jnigen", "type": 301},
{ "source": "/platform-integration/android/install-android", "destination": "/platform-integration/android/setup", "type": 301 },
{ "source": "/platform-integration/android/install-android/:rest*", "destination": "/platform-integration/android/setup", "type": 301 },
{ "source": "/platform-integration/android/splash-screen-migration", "destination": "/release/breaking-changes/splash-screen-migration", "type": 301 },
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 @@
+
+
+
\ 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 @@
+
+
+
\ No newline at end of file
diff --git a/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md b/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md
deleted file mode 100644
index d2476b9145d..00000000000
--- a/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md
+++ /dev/null
@@ -1,91 +0,0 @@
----
-title: "Calling JetPack APIs"
-description: "Use the latest Android APIs from your Dart code"
----
-
-
-
-Flutter apps running on Android can always make use of the
-latest APIs on the first day they are released on Android, no
-matter what. This page outlines available ways to invoke
-Android-specific APIs.
-
-## Use an existing solution
-
-In most scenarios, you can use a plugin (as shown in the next section)
-to invoke native APIs without writing any custom boilerplate or
-glue code yourself.
-
-### Use a plugin
-
-Using a plugin is often the easiest way to access native
-APIs, regardless of where your Flutter app is running. To
-use plugins, visit [pub.dev][pub] and search for
-the topic you need. Most native features, including accessing
-common hardware like GPS, the camera, or step counters are
-supported by robust plugins.
-
-For complete guidance on adding plugins to your Flutter app,
-see the [Using packages documentation][packages].
-
-[packages]: /packages-and-plugins/using-packages
-[pub]: {{site.pub}}
-
-Not all native features are supported by plugins, especially
-immediately after their release. In any scenario where
-your desired native feature is not covered by a package on
-[pub.dev][pub], continue on to the following sections.
-
-## Creating a custom solution
-
-Not all scenarios and APIs will be supported by
-existing solutions; but luckily, you can always add whatever
-support you need. The next sections describe two different
-ways to call native code from Dart.
-
-:::note
-Neither solution below is inherently better or worse than
-existing plugins, because all plugins use one of the following
-two options.
-:::
-
-### Call native code directly via FFI
-
-The most direct and efficient way to invoke native APIs is by
-calling the API directly, via FFI. This links your Dart executable
-to any specified native code at compile-time, allowing you to
-call it directly from the UI thread through a small amount of glue
-code. In most cases, [ffigen][ffigen] or [jnigen][jnigen] are
-helpful in writing this glue code.
-
-For complete guidance on directly calling native code from
-your Flutter app, see the [FFI documentation][ffi].
-
-In the coming months, the Dart team hopes to make this process
-easier with direct support for calling native APIs using the
-FFI approach, but without any need for the developer to write
-glue code.
-
-[ffi]: {{site.dart-site}}/interop/c-interop
-[ffigen]: {{site.pub}}/packages/ffigen
-[jnigen]: {{site.pub}}/packages/jnigen
-
-
-### Add a MethodChannel
-
-[`MethodChannel`][methodchannels-api-docs]s are an alternate
-way Flutter apps can invoke arbitrary native code.
-Unlike the FFI solution described in the previous step,
-MethodChannels are always asynchronous, which
-might or might not matter to you, depending on your use case. As
-with FFI and direct calls to native code, using a `MethodChannel`
-requires a small amount of glue code to translate your Dart objects
-into native objects, and then back again. In most cases,
-[`pkg:pigeon`][pigeon] is helpful in writing this glue code.
-
-For complete guidance on adding MethodChannels to your Flutter
-app, see the [`MethodChannel`s documentation][methodchannels].
-
-[methodchannels]: /platform-integration/platform-channels
-[methodchannels-api-docs]: {{site.api}}/flutter/services/MethodChannel-class.html
-[pigeon]: {{site.pub}}/packages/pigeon
diff --git a/sites/docs/src/content/platform-integration/android/call-native-apis.md b/sites/docs/src/content/platform-integration/android/call-native-apis.md
new file mode 100644
index 00000000000..8ec36b0d634
--- /dev/null
+++ b/sites/docs/src/content/platform-integration/android/call-native-apis.md
@@ -0,0 +1,30 @@
+---
+title: "Calling Android APIs"
+description: "How to call Android-specific APIs or native libraries from Flutter code."
+---
+
+
+
+Flutter apps running on Android can always make use of the
+latest APIs on the first day they are released on Android,
+no matter what.
+
+To choose the best integration model for your app,
+first review the [Platform-specific options guide](/platform-integration/native-code-options).
+
+## Native Android integrations
+
+Once you have selected an integration architecture,
+use the following resources to implement it on Android:
+
+### Call Java or Kotlin code directly
+To invoke standard Android APIs, Jetpack libraries, or custom JVM classes:
+* Read our guide on using [jnigen to bind Java/Kotlin code](/platform-integration/android/jnigen).
+
+### Call C or C++ NDK libraries directly
+To execute low-level C libraries or native engine code:
+* Read our guide on using [Dart FFI](/platform-integration/bind-native-code).
+
+### Communicate using a message bridge
+To build plugins or communicate asynchronously with platform-specific code:
+* Use [Pigeon or manual MethodChannels](/platform-integration/platform-channels).
diff --git a/sites/docs/src/content/platform-integration/android/compose-activity.md b/sites/docs/src/content/platform-integration/android/compose-activity.md
index 1b51f5c4c2b..688cab7f0e0 100644
--- a/sites/docs/src/content/platform-integration/android/compose-activity.md
+++ b/sites/docs/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,345 @@ 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"
+ 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 +462,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/sites/docs/src/content/platform-integration/android/jnigen.md b/sites/docs/src/content/platform-integration/android/jnigen.md
new file mode 100644
index 00000000000..90a8e982bff
--- /dev/null
+++ b/sites/docs/src/content/platform-integration/android/jnigen.md
@@ -0,0 +1,221 @@
+---
+title: "Calling Android APIs using jnigen"
+description: "Learn how to use auto-generated Dart bindings to call native Android APIs directly from your Flutter plugins."
+---
+
+## Overview
+
+In traditional Flutter development,
+calling native Android platform APIs requires mapping asynchronous message channels
+(using `MethodChannel` or `BasicMessageChannel`),
+writing Kotlin or Java host-side listeners,
+and manually serializing and deserializing types.
+
+With direct native interop using `jnigen` (Java Native Interface Generation),
+this process is simplified:
+1. **Configure**: Write a generation configuration script
+ pointing to the Android SDK or third-party classes you need.
+2. **Generate**: Run the generation package
+ to analyze native classes and compile a type-safe Dart API bridge.
+3. **Call directly**: Call Java/Kotlin code synchronously (where applicable)
+ and type-safely in your Dart code.
+
+{:width="100%"}
+
+This tutorial guides you through building a Flutter plugin called `native_toast`
+that interacts directly with Android's system `Toast` messages
+without manually writing any Kotlin code.
+
+---
+
+## Step 1: Create a plugin and add dependencies
+
+First, create a new Flutter plugin template focused on the Android platform.
+
+```sh
+flutter create --template=plugin --platforms=android native_toast
+cd native_toast
+```
+
+Now, add the mandatory runtime bindings package (`jni`),
+the Flutter hooks integration wrapper (`jni_flutter`),
+and the developer-facing generator tool (`jnigen`) as a dev dependency:
+
+```sh
+flutter pub add jni jni_flutter
+flutter pub add dev:jnigen
+```
+
+---
+
+## Step 2: Create the generation config script
+
+Create a configuration script that details which native classes `jnigen`
+needs to generate Dart models for.
+
+Specify `tool/jnigen.dart` at the root of your plugin directory:
+
+```sh
+mkdir -p tool
+touch tool/jnigen.dart
+```
+
+Add the following configuration code to `tool/jnigen.dart`:
+
+```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/src/generated/android_os.g.dart'),
+ structure: OutputStructure.singleFile,
+ ),
+ ),
+ // Automatically resolves your Android SDK path and evaluates the
+ // example app's build setup to acquire required platform dependencies.
+ androidSdkConfig: AndroidSdkConfig(
+ addGradleDeps: true,
+ androidExample: 'example/',
+ ),
+ classes: [
+ 'android.widget.Toast',
+ 'android.os.Vibrator',
+ 'android.content.Context',
+ ],
+ ),
+ );
+}
+```
+---
+
+## Step 3: Run the code generator
+
+Run the code generation script to compile your bindings:
+
+```sh
+dart run tool/jnigen.dart
+```
+
+Once execution completes,
+`jnigen` creates a type-safe bridge inside `lib/src/generated/android_os.g.dart`.
+
+---
+
+## Step 4: Implement the user-facing plugin API
+
+Now, build a clean, developer-facing API
+that imports the generated interop bindings
+and abstracts away the JNI details.
+
+Replace the contents of `lib/native_toast.dart` with the following implementation:
+
+```dart
+import 'package:jni/jni.dart';
+import 'package:jni_flutter/jni_flutter.dart';
+import 'src/generated/android_os.g.dart' as native;
+
+class NativeToast {
+ // Extracts the live Android OS context handle and casts it to our generated class
+ final native.Context _context = androidApplicationContext.as(native.Context.type);
+
+ void showToast(String message) {
+ // 1. Convert the Dart String into a native Java JString reference pointer
+ final jMessage = message.toJString();
+
+ // 2. Call the overloaded constructor generated by jnigen
+ final toast = native.Toast.makeText$1(
+ _context,
+ jMessage,
+ native.Toast.LENGTH_SHORT,
+ );
+
+ // 3. Trigger the standard Android System layout service
+ toast!.show();
+
+ // 4. Release the native JNI allocation pointer to prevent memory leaks
+ jMessage.release();
+ }
+}
+```
+
+### Understanding overloaded method mappings
+
+Java and Kotlin support method overloading (methods with the same name
+that differ by parameter count or types).
+Since Dart does not support method overloading,
+`jnigen` appends a dollar suffix (such as `$1` or `$2`)
+to disambiguate the generated Dart signatures in the order they are resolved.
+
+For example, `Toast.makeText` has two distinct signatures in Android:
+* `Toast.makeText(Context context, int resId, int duration)`
+* `Toast.makeText(Context context, CharSequence text, int duration)`
+
+In Dart, these are mapped respectively to:
+* `Toast.makeText(...)` (Default / first signature)
+* `Toast.makeText$1(...)` (Second signature, which accepts a string-mapped `CharSequence`)
+
+---
+
+## Step 5: Test in the example app
+
+Verify the plugin within the automatically generated sandbox project under `example/`.
+
+Replace the contents of `example/lib/main.dart` with:
+
+```dart
+import 'package:flutter/material.dart';
+import 'package:native_toast/native_toast.dart';
+
+void main() {
+ runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ home: Scaffold(
+ appBar: AppBar(
+ title: const Text('Modern Native Toast Demo'),
+ backgroundColor: Colors.teal,
+ ),
+ body: Center(
+ child: ElevatedButton(
+ style: ElevatedButton.styleFrom(backgroundColor: Colors.teal),
+ onPressed: () {
+ // Instantiate our type-safe plugin wrapper class
+ final toastPlugin = NativeToast();
+
+ // Call the direct Android API wrapper
+ toastPlugin.showToast('Hello from Dart JNI Extension Types!');
+ },
+ child: const Text('Trigger Native Toast', style: TextStyle(color: Colors.white)),
+ ),
+ ),
+ ),
+ );
+ }
+}
+```
+
+Connect an Android emulator or device,
+change into the example folder,
+and run:
+
+```sh
+cd example
+flutter run
+```
+
+Once the application launches,
+tap **Trigger Native Toast** to verify the system toast display:
+
+{:width="300px" style="display: block; margin: 0 auto;"}
diff --git a/sites/docs/src/content/platform-integration/android/request-permission.md b/sites/docs/src/content/platform-integration/android/request-permission.md
new file mode 100644
index 00000000000..64956ef35ad
--- /dev/null
+++ b/sites/docs/src/content/platform-integration/android/request-permission.md
@@ -0,0 +1,200 @@
+---
+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) {
+ final jPermission = permission.toJString();
+ try {
+ final result = ContextCompat.checkSelfPermission(
+ context,
+ jPermission,
+ );
+ return result == PackageManager.PERMISSION_GRANTED;
+ } finally {
+ jPermission.release();
+ }
+ }
+
+ 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/sites/docs/src/content/platform-integration/index.md b/sites/docs/src/content/platform-integration/index.md
index 712d0384a68..31712a00f4b 100644
--- a/sites/docs/src/content/platform-integration/index.md
+++ b/sites/docs/src/content/platform-integration/index.md
@@ -133,9 +133,9 @@ Learn how to add custom integrations with Android to your Flutter app.
Learn how to add the predictive back gesture to your app on Android.