[jnigen] Android plugin example (https://github.com/dart-lang/jnigen/issues/46)

diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml
index 0b4123f..9bae125 100644
--- a/.github/workflows/test-package.yml
+++ b/.github/workflows/test-package.yml
@@ -212,6 +212,33 @@
       - run: flutter pub get
       - run: flutter build apk
 
+  build_notification_plugin_example:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        working-directory: ./pkgs/jni_gen/examples/notification_plugin
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/setup-java@v2
+        with:
+          distribution: 'zulu'
+          java-version: '11'
+      - uses: subosito/flutter-action@v2
+        with:
+          channel: 'stable'
+          cache: true
+          cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
+      - run: flutter pub get
+      - run: flutter analyze
+      - run: flutter build apk
+        working-directory: ./pkgs/jni_gen/examples/notification_plugin/example
+      - name: re-generate bindings
+        run: flutter pub run jni_gen -Ddart_root=_dart -Dc_root=_c --config jnigen.yaml
+      - name: compare generated dart bindings
+        run: diff -qr lib/ _dart
+      - name: compare generated C bindings
+        run: diff -qr src/ _c
+
   build_in_app_java_example:
     runs-on: ubuntu-latest
     defaults:
diff --git a/pkgs/jni_gen/examples/in_app_java/android/app/build.gradle b/pkgs/jni_gen/examples/in_app_java/android/app/build.gradle
index 784c659..bd28789 100644
--- a/pkgs/jni_gen/examples/in_app_java/android/app/build.gradle
+++ b/pkgs/jni_gen/examples/in_app_java/android/app/build.gradle
@@ -61,7 +61,7 @@
         }
     }
 
-externalNativeBuild {
+	externalNativeBuild {
 		cmake {
 			path "../../src/android_utils/CMakeLists.txt"
 		}
diff --git a/pkgs/jni_gen/examples/in_app_java/android/app/src/main/java/com/example/in_app_java/AndroidUtils.java b/pkgs/jni_gen/examples/in_app_java/android/app/src/main/java/com/example/in_app_java/AndroidUtils.java
index 4c297bf..a998516 100644
--- a/pkgs/jni_gen/examples/in_app_java/android/app/src/main/java/com/example/in_app_java/AndroidUtils.java
+++ b/pkgs/jni_gen/examples/in_app_java/android/app/src/main/java/com/example/in_app_java/AndroidUtils.java
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 package com.example.in_app_java;
 
 import android.app.Activity;
diff --git a/pkgs/jni_gen/examples/in_app_java/lib/main.dart b/pkgs/jni_gen/examples/in_app_java/lib/main.dart
index b47cb31..10081f5 100644
--- a/pkgs/jni_gen/examples/in_app_java/lib/main.dart
+++ b/pkgs/jni_gen/examples/in_app_java/lib/main.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 import 'package:flutter/material.dart';
 import 'package:jni/jni.dart';
 
diff --git a/pkgs/jni_gen/examples/in_app_java/test/widget_test.dart b/pkgs/jni_gen/examples/in_app_java/test/widget_test.dart
deleted file mode 100644
index 0c153cc..0000000
--- a/pkgs/jni_gen/examples/in_app_java/test/widget_test.dart
+++ /dev/null
@@ -1,30 +0,0 @@
-// This is a basic Flutter widget test.
-//
-// To perform an interaction with a widget in your test, use the WidgetTester
-// utility in the flutter_test package. For example, you can send tap and scroll
-// gestures. You can also use WidgetTester to find child widgets in the widget
-// tree, read text, and verify that the values of widget properties are correct.
-
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-import 'package:in_app_java/main.dart';
-
-void main() {
-  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
-    // Build our app and trigger a frame.
-    await tester.pumpWidget(const MyApp());
-
-    // Verify that our counter starts at 0.
-    expect(find.text('0'), findsOneWidget);
-    expect(find.text('1'), findsNothing);
-
-    // Tap the '+' icon and trigger a frame.
-    await tester.tap(find.byIcon(Icons.add));
-    await tester.pump();
-
-    // Verify that our counter has incremented.
-    expect(find.text('0'), findsNothing);
-    expect(find.text('1'), findsOneWidget);
-  });
-}
diff --git a/pkgs/jni_gen/examples/in_app_java/tool/generate_bindings.dart b/pkgs/jni_gen/examples/in_app_java/tool/generate_bindings.dart
index 2628d99..cb2b53d 100644
--- a/pkgs/jni_gen/examples/in_app_java/tool/generate_bindings.dart
+++ b/pkgs/jni_gen/examples/in_app_java/tool/generate_bindings.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 import 'package:jni_gen/jni_gen.dart';
 
 void main(List<String> args) async {
@@ -8,7 +12,7 @@
     dartRoot: Uri.directory('lib/android_utils'),
     libraryName: 'android_utils',
     androidSdkConfig: AndroidSdkConfig(
-      versions: [31],
+      addGradleDeps: true,
     ),
   );
   await generateJniBindings(config);
diff --git a/pkgs/jni_gen/examples/notification_plugin/.gitignore b/pkgs/jni_gen/examples/notification_plugin/.gitignore
new file mode 100644
index 0000000..96486fd
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/.gitignore
@@ -0,0 +1,30 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+.packages
+build/
diff --git a/pkgs/jni_gen/examples/notification_plugin/.metadata b/pkgs/jni_gen/examples/notification_plugin/.metadata
new file mode 100644
index 0000000..d677252
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/.metadata
@@ -0,0 +1,30 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled.
+
+version:
+  revision: ffccd96b62ee8cec7740dab303538c5fc26ac543
+  channel: stable
+
+project_type: plugin_ffi
+
+# Tracks metadata for the flutter migrate command
+migration:
+  platforms:
+    - platform: root
+      create_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543
+      base_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543
+    - platform: android
+      create_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543
+      base_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543
+
+  # User provided section
+
+  # List of Local paths (relative to this file) that should be
+  # ignored by the migrate tool.
+  #
+  # Files that are not part of the templates will be ignored by default.
+  unmanaged_files:
+    - 'lib/main.dart'
+    - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/pkgs/jni_gen/examples/notification_plugin/README.md b/pkgs/jni_gen/examples/notification_plugin/README.md
new file mode 100644
index 0000000..1fcccdc
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/README.md
@@ -0,0 +1,13 @@
+# notification_plugin
+
+Example of android plugin project with jni_gen.
+
+This plugin project contains [custom code](android/src/main/java/com/example/notification_plugin) which uses the Android libraries. The bindings are generated using [jnigen config](jnigen.yaml) and then used in [flutter example](example/lib/main.dart), with help of `package:jni` APIs.
+
+The command to regenerate JNI bindings is:
+```
+flutter run jni_gen --config jnigen.yaml # run from notification_plugin project root 
+```
+
+The `example/` app must be built at least once in _release_ mode (eg `flutter build apk`) before running jni_gen. This is the equivalent of Gradle Sync in Android Studio, and enables `jni_gen` to run a Gradle stub and determine release build's classpath, which contains the paths to relevant dependencies. Therefore a build must have been run after cleaning build directories, or updating Java dependencies. This is a known complexity of the Gradle build system, and if you know a solution, please contribute to issue discussion at #33.
+
diff --git a/pkgs/jni_gen/examples/notification_plugin/analysis_options.yaml b/pkgs/jni_gen/examples/notification_plugin/analysis_options.yaml
new file mode 100644
index 0000000..a5744c1
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/analysis_options.yaml
@@ -0,0 +1,4 @@
+include: package:flutter_lints/flutter.yaml
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/jni_gen/examples/notification_plugin/android/.gitignore b/pkgs/jni_gen/examples/notification_plugin/android/.gitignore
new file mode 100644
index 0000000..161bdcd
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/android/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.cxx
diff --git a/pkgs/jni_gen/examples/notification_plugin/android/build.gradle b/pkgs/jni_gen/examples/notification_plugin/android/build.gradle
new file mode 100644
index 0000000..7643ec5
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/android/build.gradle
@@ -0,0 +1,59 @@
+// The Android Gradle Plugin builds the native code with the Android NDK.
+
+group 'com.example.notification_plugin'
+version '1.0'
+
+buildscript {
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        // The Android Gradle Plugin knows how to build native code with the NDK.
+        classpath 'com.android.tools.build:gradle:7.1.2'
+    }
+}
+
+rootProject.allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+apply plugin: 'com.android.library'
+
+android {
+    // Bumping the plugin compileSdkVersion requires all clients of this plugin
+    // to bump the version in their app.
+    compileSdkVersion 31
+
+    // Bumping the plugin ndkVersion requires all clients of this plugin to bump
+    // the version in their app and to download a newer version of the NDK.
+    ndkVersion "21.1.6352462"
+
+    // Invoke the shared CMake build with the Android Gradle Plugin.
+    externalNativeBuild {
+        cmake {
+            path "../src/CMakeLists.txt"
+
+            // The default CMake version for the Android Gradle Plugin is 3.10.2.
+            // https://developer.android.com/studio/projects/install-ndk#vanilla_cmake
+            //
+            // The Flutter tooling requires that developers have CMake 3.10 or later
+            // installed. You should not increase this version, as doing so will cause
+            // the plugin to fail to compile for some customers of the plugin.
+            // version "3.10.2"
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    defaultConfig {
+        minSdkVersion 16
+    }
+}
diff --git a/pkgs/jni_gen/examples/notification_plugin/android/settings.gradle b/pkgs/jni_gen/examples/notification_plugin/android/settings.gradle
new file mode 100644
index 0000000..8e0a5b1
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'notification_plugin'
diff --git a/pkgs/jni_gen/examples/notification_plugin/android/src/main/AndroidManifest.xml b/pkgs/jni_gen/examples/notification_plugin/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..01cb165
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="com.example.notification_plugin">
+</manifest>
diff --git a/pkgs/jni_gen/examples/notification_plugin/android/src/main/java/com/example/notification_plugin/Notifications.java b/pkgs/jni_gen/examples/notification_plugin/android/src/main/java/com/example/notification_plugin/Notifications.java
new file mode 100644
index 0000000..59f8b7a
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/android/src/main/java/com/example/notification_plugin/Notifications.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.example.notification_plugin;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Build;
+import androidx.annotation.Keep;
+import androidx.core.app.NotificationCompat;
+
+@Keep
+public class Notifications {
+  public static void showNotification(
+      Context context, int notificationID, String title, String text) {
+    @SuppressWarnings("deprecation")
+    NotificationCompat.Builder builder =
+        new NotificationCompat.Builder(context)
+            .setSmallIcon(android.R.drawable.ic_dialog_info)
+            .setContentTitle(title)
+            .setContentText(text);
+    NotificationManager notificationManager =
+        (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
+      String channelId = "my_channel";
+      NotificationChannel channel =
+          new NotificationChannel(
+              channelId, "My App's notifications", NotificationManager.IMPORTANCE_HIGH);
+      notificationManager.createNotificationChannel(channel);
+      builder.setChannelId(channelId);
+    }
+    notificationManager.notify(notificationID, builder.build());
+  }
+}
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/.gitignore b/pkgs/jni_gen/examples/notification_plugin/example/.gitignore
new file mode 100644
index 0000000..24476c5
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/.gitignore
@@ -0,0 +1,44 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/README.md b/pkgs/jni_gen/examples/notification_plugin/example/README.md
new file mode 100644
index 0000000..0ec9e1d
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/README.md
@@ -0,0 +1,4 @@
+# notification_plugin_example
+
+This app demonstrates how to use the notification_plugin plugin.
+
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/analysis_options.yaml b/pkgs/jni_gen/examples/notification_plugin/example/analysis_options.yaml
new file mode 100644
index 0000000..61b6c4d
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/analysis_options.yaml
@@ -0,0 +1,29 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+  # The lint rules applied to this project can be customized in the
+  # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+  # included above or to enable additional rules. A list of all available lints
+  # and their documentation is published at
+  # https://dart-lang.github.io/linter/lints/index.html.
+  #
+  # Instead of disabling a lint rule for the entire project in the
+  # section below, it can also be suppressed for a single line of code
+  # or a specific dart file by using the `// ignore: name_of_lint` and
+  # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+  # producing the lint.
+  rules:
+    # avoid_print: false  # Uncomment to disable the `avoid_print` rule
+    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/.gitignore b/pkgs/jni_gen/examples/notification_plugin/example/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/build.gradle b/pkgs/jni_gen/examples/notification_plugin/example/android/app/build.gradle
new file mode 100644
index 0000000..ee3c62f
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/build.gradle
@@ -0,0 +1,71 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+    localPropertiesFile.withReader('UTF-8') { reader ->
+        localProperties.load(reader)
+    }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    compileSdkVersion flutter.compileSdkVersion
+    ndkVersion flutter.ndkVersion
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
+    }
+
+    defaultConfig {
+        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+        applicationId "com.example.notification_plugin_example"
+        // You can update the following values to match your application needs.
+        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
+        minSdkVersion flutter.minSdkVersion
+        targetSdkVersion flutter.targetSdkVersion
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
+    }
+
+    buildTypes {
+        release {
+            // TODO: Add your own signing config for the release build.
+            // Signing with the debug keys for now, so `flutter run --release` works.
+            signingConfig signingConfigs.debug
+        }
+    }
+}
+
+flutter {
+    source '../..'
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/debug/AndroidManifest.xml b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..822a208
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.notification_plugin_example">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/AndroidManifest.xml b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..60e7eab
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.notification_plugin_example">
+   <application
+        android:label="notification_plugin_example"
+        android:name="${applicationName}"
+        android:icon="@mipmap/ic_launcher">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:launchMode="singleTop"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+            <!-- Specifies an Android theme to apply to this Activity as soon as
+                 the Android process has started. This theme is visible to the user
+                 while the Flutter UI initializes. After that, this theme continues
+                 to determine the Window background behind the Flutter UI. -->
+            <meta-data
+              android:name="io.flutter.embedding.android.NormalTheme"
+              android:resource="@style/NormalTheme"
+              />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <!-- Don't delete the meta-data below.
+             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+        <meta-data
+            android:name="flutterEmbedding"
+            android:value="2" />
+    </application>
+</manifest>
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/kotlin/com/example/notification_plugin_example/MainActivity.kt b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/kotlin/com/example/notification_plugin_example/MainActivity.kt
new file mode 100644
index 0000000..af88e15
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/kotlin/com/example/notification_plugin_example/MainActivity.kt
@@ -0,0 +1,6 @@
+package com.example.notification_plugin_example
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="?android:colorBackground" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/drawable/launch_background.xml b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/values-night/styles.xml b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/values/styles.xml b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/profile/AndroidManifest.xml b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..822a208
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.notification_plugin_example">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/build.gradle b/pkgs/jni_gen/examples/notification_plugin/example/android/build.gradle
new file mode 100644
index 0000000..83ae220
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/build.gradle
@@ -0,0 +1,31 @@
+buildscript {
+    ext.kotlin_version = '1.6.10'
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:7.1.2'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+    project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+    project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/gradle.properties b/pkgs/jni_gen/examples/notification_plugin/example/android/gradle.properties
new file mode 100644
index 0000000..94adc3a
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/gradle/wrapper/gradle-wrapper.properties b/pkgs/jni_gen/examples/notification_plugin/example/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..cb24abd
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/settings.gradle b/pkgs/jni_gen/examples/notification_plugin/example/android/settings.gradle
new file mode 100644
index 0000000..44e62bc
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/android/settings.gradle
@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/lib/main.dart b/pkgs/jni_gen/examples/notification_plugin/example/lib/main.dart
new file mode 100644
index 0000000..4bc30d4
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/lib/main.dart
@@ -0,0 +1,89 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'package:jni/jni.dart';
+
+// The hierarchy created in generated code will mirror the java package
+// structure. This is an implementation convenience and we may allow
+// more customization in future.
+import 'package:notification_plugin/com/example/notification_plugin.dart';
+
+JlObject activity = JlObject.fromRef(Jni.getInstance().getCurrentActivity());
+
+int i = 0;
+
+void showNotification(String title, String text) {
+  i = i + 1;
+  var jTitle = JlString.fromString(title);
+  var jText = JlString.fromString(text);
+  Notifications.showNotification(activity, i, jTitle, jText);
+  jTitle.delete();
+  jText.delete();
+}
+
+void main() {
+  runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+  const MyApp({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: 'Flutter Demo',
+      theme: ThemeData(
+        primarySwatch: Colors.teal,
+      ),
+      home: MyHomePage(title: 'Flutter Demo Home Page'),
+    );
+  }
+}
+
+class MyHomePage extends StatelessWidget {
+  MyHomePage({Key? key, required this.title}) : super(key: key);
+
+  final String title;
+
+  final _title = TextEditingController(text: 'Hello from JNI');
+  final _text = TextEditingController(text: '😀');
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(title),
+      ),
+      body: Center(
+        child: Padding(
+          padding: const EdgeInsets.all(16),
+          child: Column(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              TextFormField(
+                controller: _title,
+                textCapitalization: TextCapitalization.sentences,
+                decoration:
+                    const InputDecoration(labelText: 'Notification title'),
+              ),
+              TextFormField(
+                controller: _text,
+                keyboardType: TextInputType.multiline,
+                minLines: 1,
+                maxLines: 4,
+                decoration:
+                    const InputDecoration(labelText: 'Notification text'),
+              ),
+              ElevatedButton(
+                child: const Text('Show Notification'),
+                onPressed: () => showNotification(_title.text, _text.text),
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/pubspec.yaml b/pkgs/jni_gen/examples/notification_plugin/example/pubspec.yaml
new file mode 100644
index 0000000..b1f6a97
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/example/pubspec.yaml
@@ -0,0 +1,89 @@
+name: notification_plugin_example
+description: Demonstrates how to use the notification_plugin plugin.
+
+# The following line prevents the package from being accidentally published to
+# pub.dev using `flutter pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+# The following defines the version and build number for your application.
+# A version number is three numbers separated by dots, like 1.2.43
+# followed by an optional build number separated by a +.
+# Both the version and the builder number may be overridden in flutter
+# build by specifying --build-name and --build-number, respectively.
+# In Android, build-name is used as versionName while build-number used as versionCode.
+# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
+# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
+# Read more about iOS versioning at
+# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
+# In Windows, build-name is used as the major, minor, and patch parts
+# of the product and file versions while build-number is used as the build suffix.
+version: 1.0.0+1
+
+environment:
+  sdk: '>=2.18.0 <3.0.0'
+
+dependencies:
+  flutter:
+    sdk: flutter
+
+  notification_plugin:
+    path: ../
+
+  jni:
+    path: ../../../../jni/
+  # The following adds the Cupertino Icons font to your application.
+  # Use with the CupertinoIcons class for iOS style icons.
+  cupertino_icons: ^1.0.2
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+
+  # The "flutter_lints" package below contains a set of recommended lints to
+  # encourage good coding practices. The lint set provided by the package is
+  # activated in the `analysis_options.yaml` file located at the root of your
+  # package. See that file for information about deactivating specific lint
+  # rules and activating additional ones.
+  flutter_lints: ^2.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+
+  # The following line ensures that the Material Icons font is
+  # included with your application, so that you can use the icons in
+  # the material Icons class.
+  uses-material-design: true
+
+  # To add assets to your application, add an assets section, like this:
+  # assets:
+  #   - images/a_dot_burr.jpeg
+  #   - images/a_dot_ham.jpeg
+
+  # An image asset can refer to one or more resolution-specific "variants", see
+  # https://flutter.dev/assets-and-images/#resolution-aware
+
+  # For details regarding adding assets from package dependencies, see
+  # https://flutter.dev/assets-and-images/#from-packages
+
+  # To add custom fonts to your application, add a fonts section here,
+  # in this "flutter" section. Each entry in this list should have a
+  # "family" key with the font family name, and a "fonts" key with a
+  # list giving the asset and other descriptors for the font. For
+  # example:
+  # fonts:
+  #   - family: Schyler
+  #     fonts:
+  #       - asset: fonts/Schyler-Regular.ttf
+  #       - asset: fonts/Schyler-Italic.ttf
+  #         style: italic
+  #   - family: Trajan Pro
+  #     fonts:
+  #       - asset: fonts/TrajanPro.ttf
+  #       - asset: fonts/TrajanPro_Bold.ttf
+  #         weight: 700
+  #
+  # For details regarding fonts from package dependencies,
+  # see https://flutter.dev/custom-fonts/#from-packages
diff --git a/pkgs/jni_gen/examples/notification_plugin/jnigen.yaml b/pkgs/jni_gen/examples/notification_plugin/jnigen.yaml
new file mode 100644
index 0000000..16315f6
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/jnigen.yaml
@@ -0,0 +1,17 @@
+android_sdk_config:
+  add_gradle_deps: true
+  android_example: 'example/'
+
+preamble: |
+  // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+  // for details. All rights reserved. Use of this source code is governed by a
+  // BSD-style license that can be found in the LICENSE file.
+
+library_name: notification_plugin
+source_path:
+  - 'android/src/main/java'
+classes:
+  - 'com.example.notification_plugin.Notifications'
+c_root: src/
+dart_root: lib/
+
diff --git a/pkgs/jni_gen/examples/notification_plugin/lib/com/example/notification_plugin.dart b/pkgs/jni_gen/examples/notification_plugin/lib/com/example/notification_plugin.dart
new file mode 100644
index 0000000..52c75af
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/lib/com/example/notification_plugin.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Autogenerated by jni_gen. DO NOT EDIT!
+
+// ignore_for_file: camel_case_types
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: annotate_overrides
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: unused_element
+
+import "dart:ffi" as ffi;
+
+import "package:jni/jni.dart" as jni;
+
+import "../../init.dart" show jlookup;
+
+/// from: com.example.notification_plugin.Notifications
+class Notifications extends jni.JlObject {
+  Notifications.fromRef(ffi.Pointer<ffi.Void> ref) : super.fromRef(ref);
+
+  static final _ctor =
+      jlookup<ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>>(
+              "com_example_notification_plugin_Notifications_ctor")
+          .asFunction<ffi.Pointer<ffi.Void> Function()>();
+
+  /// from: public void <init>()
+  Notifications() : super.fromRef(_ctor());
+
+  static final _showNotification = jlookup<
+              ffi.NativeFunction<
+                  ffi.Void Function(ffi.Pointer<ffi.Void>, ffi.Int32,
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "com_example_notification_plugin_Notifications_showNotification")
+      .asFunction<
+          void Function(ffi.Pointer<ffi.Void>, int, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public void showNotification(android.content.Context context, int notificationID, java.lang.String title, java.lang.String text)
+  static void showNotification(jni.JlObject context, int notificationID,
+          jni.JlString title, jni.JlString text) =>
+      _showNotification(
+          context.reference, notificationID, title.reference, text.reference);
+}
diff --git a/pkgs/jni_gen/examples/notification_plugin/lib/init.dart b/pkgs/jni_gen/examples/notification_plugin/lib/init.dart
new file mode 100644
index 0000000..9a4243f
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/lib/init.dart
@@ -0,0 +1,5 @@
+import "dart:ffi";
+import "package:jni/jni.dart";
+
+final Pointer<T> Function<T extends NativeType>(String sym) jlookup =
+    Jni.getInstance().initGeneratedLibrary("notification_plugin");
diff --git a/pkgs/jni_gen/examples/notification_plugin/pubspec.yaml b/pkgs/jni_gen/examples/notification_plugin/pubspec.yaml
new file mode 100644
index 0000000..422f83a
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/pubspec.yaml
@@ -0,0 +1,76 @@
+name: notification_plugin
+description: A new Flutter FFI plugin project.
+version: 0.0.1
+publish_to: none
+homepage: https://github.com/dart-lang/jni_gen
+
+environment:
+  sdk: '>=2.18.0 <3.0.0'
+  flutter: ">=2.11.0"
+
+dependencies:
+  jni:
+    path: ../../../jni/
+  flutter:
+    sdk: flutter
+  plugin_platform_interface: ^2.0.2
+
+dev_dependencies:
+  jni_gen:
+    path: ../../
+  flutter_test:
+    sdk: flutter
+  flutter_lints: ^2.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+  # This section identifies this Flutter project as a plugin project.
+  # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
+  # which should be registered in the plugin registry. This is required for
+  # using method channels.
+  # The Android 'package' specifies package in which the registered class is.
+  # This is required for using method channels on Android.
+  # The 'ffiPlugin' specifies that native code should be built and bundled.
+  # This is required for using `dart:ffi`.
+  # All these are used by the tooling to maintain consistency when
+  # adding or updating assets for this project.
+  #
+  # Please refer to README.md for a detailed explanation.
+  plugin:
+    platforms:
+      android:
+        ffiPlugin: true
+
+  # To add assets to your plugin package, add an assets section, like this:
+  # assets:
+  #   - images/a_dot_burr.jpeg
+  #   - images/a_dot_ham.jpeg
+  #
+  # For details regarding assets in packages, see
+  # https://flutter.dev/assets-and-images/#from-packages
+  #
+  # An image asset can refer to one or more resolution-specific "variants", see
+  # https://flutter.dev/assets-and-images/#resolution-aware
+
+  # To add custom fonts to your plugin package, add a fonts section here,
+  # in this "flutter" section. Each entry in this list should have a
+  # "family" key with the font family name, and a "fonts" key with a
+  # list giving the asset and other descriptors for the font. For
+  # example:
+  # fonts:
+  #   - family: Schyler
+  #     fonts:
+  #       - asset: fonts/Schyler-Regular.ttf
+  #       - asset: fonts/Schyler-Italic.ttf
+  #         style: italic
+  #   - family: Trajan Pro
+  #     fonts:
+  #       - asset: fonts/TrajanPro.ttf
+  #       - asset: fonts/TrajanPro_Bold.ttf
+  #         weight: 700
+  #
+  # For details regarding fonts in packages, see
+  # https://flutter.dev/custom-fonts/#from-packages
diff --git a/pkgs/jni_gen/examples/notification_plugin/src/CMakeLists.txt b/pkgs/jni_gen/examples/notification_plugin/src/CMakeLists.txt
new file mode 100644
index 0000000..c733845
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/src/CMakeLists.txt
@@ -0,0 +1,30 @@
+# The Flutter tooling requires that developers have CMake 3.10 or later
+# installed. You should not increase this version, as doing so will cause
+# the plugin to fail to compile for some customers of the plugin.
+cmake_minimum_required(VERSION 3.10)
+
+project(notification_plugin VERSION 0.0.1 LANGUAGES C)
+
+add_library(notification_plugin SHARED
+  "./notification_plugin.c"
+)
+
+set_target_properties(notification_plugin PROPERTIES
+  OUTPUT_NAME "notification_plugin"
+)
+
+target_compile_definitions(notification_plugin PUBLIC DART_SHARED_LIB)
+
+if(WIN32)
+	set_target_properties(${TARGET_NAME} PROPERTIES
+		LINK_FLAGS "/DELAYLOAD:jvm.dll")
+endif()
+
+if (ANDROID)
+	target_link_libraries(notification_plugin log)
+else()
+	find_package(Java REQUIRED)
+	find_package(JNI REQUIRED)
+	include_directories(${JNI_INCLUDE_DIRS})
+	target_link_libraries(notification_plugin ${JNI_LIBRARIES})
+endif()
diff --git a/pkgs/jni_gen/examples/notification_plugin/src/dartjni.h b/pkgs/jni_gen/examples/notification_plugin/src/dartjni.h
new file mode 100644
index 0000000..cd94b15
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/src/dartjni.h
@@ -0,0 +1,177 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include <jni.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if _WIN32
+#include <windows.h>
+#else
+#include <pthread.h>
+#include <unistd.h>
+#endif
+
+#if _WIN32
+#define FFI_PLUGIN_EXPORT __declspec(dllexport)
+#else
+#define FFI_PLUGIN_EXPORT
+#endif
+
+#if defined _WIN32
+#define thread_local __declspec(thread)
+#else
+#define thread_local __thread
+#endif
+
+#ifdef __ANDROID__
+#include <android/log.h>
+#endif
+
+#define JNI_LOG_TAG "Dart-JNI"
+
+#ifdef __ANDROID__
+#define __ENVP_CAST (JNIEnv **)
+#else
+#define __ENVP_CAST (void **)
+#endif
+
+struct jni_context {
+	JavaVM *jvm;
+	jobject classLoader;
+	jmethodID loadClassMethod;
+	jobject currentActivity;
+	jobject appContext;
+};
+
+extern thread_local JNIEnv *jniEnv;
+
+extern struct jni_context jni;
+
+enum DartJniLogLevel {
+	JNI_VERBOSE = 2,
+	JNI_DEBUG,
+	JNI_INFO,
+	JNI_WARN,
+	JNI_ERROR
+};
+
+FFI_PLUGIN_EXPORT struct jni_context GetJniContext();
+
+FFI_PLUGIN_EXPORT JavaVM *GetJavaVM(void);
+
+FFI_PLUGIN_EXPORT JNIEnv *GetJniEnv(void);
+
+FFI_PLUGIN_EXPORT JNIEnv *SpawnJvm(JavaVMInitArgs *args);
+
+FFI_PLUGIN_EXPORT jclass LoadClass(const char *name);
+
+FFI_PLUGIN_EXPORT jobject GetClassLoader(void);
+
+FFI_PLUGIN_EXPORT jobject GetApplicationContext(void);
+
+FFI_PLUGIN_EXPORT jobject GetCurrentActivity(void);
+
+FFI_PLUGIN_EXPORT void SetJNILogging(int level);
+
+FFI_PLUGIN_EXPORT jstring ToJavaString(char *str);
+
+FFI_PLUGIN_EXPORT const char *GetJavaStringChars(jstring jstr);
+
+FFI_PLUGIN_EXPORT void ReleaseJavaStringChars(jstring jstr, const char *buf);
+
+// These 2 are the function pointer variables defined and exported by
+// the generated C files.
+//
+// initGeneratedLibrary function in Jni class will set these to
+// corresponding functions to the implementations from `dartjni` base library
+// which initializes and manages the JNI.
+extern struct jni_context (*context_getter)(void);
+extern JNIEnv *(*env_getter)(void);
+
+// This function will be exported by generated code library and will set the
+// above 2 variables.
+FFI_PLUGIN_EXPORT void setJniGetters(struct jni_context (*cg)(void),
+		JNIEnv *(*eg)(void));
+
+// `static inline` because `inline` doesn't work, it may still not
+// inline the function in which case a linker error may be produced.
+//
+// There has to be a better way to do this. Either to force inlining on target
+// platforms, or just leave it as normal function.
+static inline void __load_class_into(jclass *cls, const char *name) {
+#ifdef __ANDROID__
+	jstring className = (*jniEnv)->NewStringUTF(jniEnv, name);
+	*cls = (*jniEnv)->CallObjectMethod(jniEnv, jni.classLoader,
+	                                   jni.loadClassMethod, className);
+	(*jniEnv)->DeleteLocalRef(jniEnv, className);
+#else
+	*cls = (*jniEnv)->FindClass(jniEnv, name);
+#endif
+}
+
+static inline void load_class(jclass *cls, const char *name) {
+	if (*cls == NULL) {
+		__load_class_into(cls, name);
+	}
+}
+
+static inline void load_class_gr(jclass *cls, const char *name) {
+	if (*cls == NULL) {
+		jclass tmp;
+		__load_class_into(&tmp, name);
+		*cls = (*jniEnv)->NewGlobalRef(jniEnv, tmp);
+		(*jniEnv)->DeleteLocalRef(jniEnv, tmp);
+	}
+}
+
+static inline void attach_thread() {
+	if (jniEnv == NULL) {
+		(*jni.jvm)->AttachCurrentThread(jni.jvm, __ENVP_CAST & jniEnv,
+		                                NULL);
+	}
+}
+
+static inline void load_env() {
+	if (jniEnv == NULL) {
+		jni = context_getter();
+		jniEnv = env_getter();
+	}
+}
+
+static inline void load_method(jclass cls, jmethodID *res, const char *name,
+                               const char *sig) {
+	if (*res == NULL) {
+		*res = (*jniEnv)->GetMethodID(jniEnv, cls, name, sig);
+	}
+}
+
+static inline void load_static_method(jclass cls, jmethodID *res,
+                                      const char *name, const char *sig) {
+	if (*res == NULL) {
+		*res = (*jniEnv)->GetStaticMethodID(jniEnv, cls, name, sig);
+	}
+}
+
+static inline void load_field(jclass cls, jfieldID *res, const char *name,
+                              const char *sig) {
+	if (*res == NULL) {
+		*res = (*jniEnv)->GetFieldID(jniEnv, cls, name, sig);
+	}
+}
+
+static inline void load_static_field(jclass cls, jfieldID *res,
+                                     const char *name, const char *sig) {
+	if (*res == NULL) {
+		*res = (*jniEnv)->GetStaticFieldID(jniEnv, cls, name, sig);
+	}
+}
+
+static inline jobject to_global_ref(jobject ref) {
+	jobject g = (*jniEnv)->NewGlobalRef(jniEnv, ref);
+	(*jniEnv)->DeleteLocalRef(jniEnv, ref);
+	return g;
+}
+
diff --git a/pkgs/jni_gen/examples/notification_plugin/src/notification_plugin.c b/pkgs/jni_gen/examples/notification_plugin/src/notification_plugin.c
new file mode 100644
index 0000000..6457cae
--- /dev/null
+++ b/pkgs/jni_gen/examples/notification_plugin/src/notification_plugin.c
@@ -0,0 +1,44 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Autogenerated by jni_gen. DO NOT EDIT!
+
+#include <stdint.h>
+#include "jni.h"
+#include "dartjni.h"
+
+thread_local JNIEnv *jniEnv;
+struct jni_context jni;
+
+struct jni_context (*context_getter)(void);
+JNIEnv *(*env_getter)(void);
+
+void setJniGetters(struct jni_context (*cg)(void),
+        JNIEnv *(*eg)(void)) {
+    context_getter = cg;
+    env_getter = eg;
+}
+
+// com.example.notification_plugin.Notifications
+jclass _c_com_example_notification_plugin_Notifications = NULL;
+
+jmethodID _m_com_example_notification_plugin_Notifications_ctor = NULL;
+FFI_PLUGIN_EXPORT
+jobject com_example_notification_plugin_Notifications_ctor() {
+    load_env();
+    load_class_gr(&_c_com_example_notification_plugin_Notifications, "com/example/notification_plugin/Notifications");
+    load_method(_c_com_example_notification_plugin_Notifications, &_m_com_example_notification_plugin_Notifications_ctor, "<init>", "()V");
+    jobject _result = (*jniEnv)->NewObject(jniEnv, _c_com_example_notification_plugin_Notifications, _m_com_example_notification_plugin_Notifications_ctor);
+    return to_global_ref(_result);
+}
+
+jmethodID _m_com_example_notification_plugin_Notifications_showNotification = NULL;
+FFI_PLUGIN_EXPORT
+void com_example_notification_plugin_Notifications_showNotification(jobject context, int32_t notificationID, jobject title, jobject text) {
+    load_env();
+    load_class_gr(&_c_com_example_notification_plugin_Notifications, "com/example/notification_plugin/Notifications");
+    load_static_method(_c_com_example_notification_plugin_Notifications, &_m_com_example_notification_plugin_Notifications_showNotification, "showNotification", "(Landroid/content/Context;ILjava/lang/String;Ljava/lang/String;)V");
+    (*jniEnv)->CallStaticVoidMethod(jniEnv, _c_com_example_notification_plugin_Notifications, _m_com_example_notification_plugin_Notifications_showNotification, context, notificationID, title, text);
+}
+
diff --git a/pkgs/jni_gen/examples/pdfbox_plugin/dart_example/bin/pdf_info.dart b/pkgs/jni_gen/examples/pdfbox_plugin/dart_example/bin/pdf_info.dart
index f8756ff..f276ea1 100644
--- a/pkgs/jni_gen/examples/pdfbox_plugin/dart_example/bin/pdf_info.dart
+++ b/pkgs/jni_gen/examples/pdfbox_plugin/dart_example/bin/pdf_info.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 import 'dart:io';
 
 import 'package:path/path.dart';
diff --git a/pkgs/jni_gen/examples/pdfbox_plugin/example/lib/main.dart b/pkgs/jni_gen/examples/pdfbox_plugin/example/lib/main.dart
index 1557b84..09527fb 100644
--- a/pkgs/jni_gen/examples/pdfbox_plugin/example/lib/main.dart
+++ b/pkgs/jni_gen/examples/pdfbox_plugin/example/lib/main.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 import 'package:flutter/material.dart';
 import 'dart:io';
 import 'dart:async';
diff --git a/pkgs/jni_gen/examples/pdfbox_plugin/tool/generate_bindings.dart b/pkgs/jni_gen/examples/pdfbox_plugin/tool/generate_bindings.dart
index b7ea48e..547a207 100644
--- a/pkgs/jni_gen/examples/pdfbox_plugin/tool/generate_bindings.dart
+++ b/pkgs/jni_gen/examples/pdfbox_plugin/tool/generate_bindings.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 import 'package:jni_gen/jni_gen.dart';
 
 const preamble = '''
@@ -31,7 +35,9 @@
     ),
     classes: ['org.apache.pdfbox.pdmodel', 'org.apache.pdfbox.text'],
     cRoot: Uri.directory('src/'),
-    dartRoot: Uri.directory('lib/'),
+    cSubdir: 'third_party',
+    preamble: preamble,
+    dartRoot: Uri.directory('lib/third_party'),
     libraryName: 'pdfbox_plugin',
   );
   await generateJniBindings(config);
diff --git a/pkgs/jni_gen/lib/src/config/config.dart b/pkgs/jni_gen/lib/src/config/config.dart
index 278e1bd..6d18085 100644
--- a/pkgs/jni_gen/lib/src/config/config.dart
+++ b/pkgs/jni_gen/lib/src/config/config.dart
@@ -56,20 +56,49 @@
 /// environment variable `ANDROID_SDK_ROOT` if it's defined, else an error
 /// will be thrown.
 class AndroidSdkConfig {
-  AndroidSdkConfig(
-      {this.versions,
-      this.sdkRoot,
-      this.includeSources = false,
-      this.addGradleDeps = false}) {
+  AndroidSdkConfig({
+    this.versions,
+    this.sdkRoot,
+    this.includeSources = false,
+    this.addGradleDeps = false,
+    this.androidExample,
+  }) {
     if (versions == null && !addGradleDeps) {
       throw ArgumentError('Neither any SDK versions nor `addGradleDeps` '
           'is specified. Unable to find Android libraries.');
     }
   }
+
+  /// Versions of android SDK to search for, in decreasing order of preference.
   List<int>? versions;
+
+  /// Root of Android SDK installation, this should be normally given on
+  /// command line or by setting `ANDROID_SDK_ROOT`, since this varies from
+  /// system to system.
   String? sdkRoot;
+
+  /// Include downloaded android SDK sources in source path.
   bool includeSources;
+
+  /// Attempt to determine exact compile time dependencies by running a gradle
+  /// stub in android subproject of this project.
+  ///
+  /// An Android build must have happened before we are able to obtain classpath
+  /// of Gradle dependencies. Run `flutter build apk` before running a jni_gen
+  /// script with this option.
+  ///
+  /// For the same reason, if the flutter project is a plugin instead of
+  /// application, it's not possible to determine the build classpath directly.
+  /// Please provide [androidExample] pointing to an example application in
+  /// that case.
   bool addGradleDeps;
+
+  /// Relative path to example application which will be used to determine
+  /// compile time classpath using a gradle stub. For most Android plugin
+  /// packages, 'example' will be the name of example application created inside
+  /// the package. This example should be built once before using this option,
+  /// so that gradle would have resolved all the dependencies.
+  String? androidExample;
 }
 
 /// Additional options to pass to the summary generator component.
@@ -263,6 +292,7 @@
               includeSources:
                   prov.getBool(_Props.includeAndroidSources) ?? false,
               addGradleDeps: prov.getBool(_Props.addGradleDeps) ?? false,
+              androidExample: prov.getString(_Props.androidExample),
             )
           : null,
     );
@@ -315,4 +345,5 @@
   static const androidSdkVersions = '$androidSdkConfig.versions';
   static const includeAndroidSources = '$androidSdkConfig.include_sources';
   static const addGradleDeps = '$androidSdkConfig.add_gradle_deps';
+  static const androidExample = '$androidSdkConfig.android_example';
 }
diff --git a/pkgs/jni_gen/lib/src/generate_bindings.dart b/pkgs/jni_gen/lib/src/generate_bindings.dart
index 09f948f..c6f410e 100644
--- a/pkgs/jni_gen/lib/src/generate_bindings.dart
+++ b/pkgs/jni_gen/lib/src/generate_bindings.dart
@@ -45,7 +45,8 @@
 
   final androidConfig = config.androidSdkConfig;
   if (androidConfig != null && androidConfig.addGradleDeps) {
-    final deps = AndroidSdkTools.getGradleClasspaths();
+    final deps = AndroidSdkTools.getGradleClasspaths(
+        androidConfig.androidExample ?? '.');
     extraJars.addAll(deps.map(Uri.file));
   }
   if (androidConfig != null && androidConfig.versions != null) {
diff --git a/pkgs/jni_gen/lib/src/tools/android_sdk_tools.dart b/pkgs/jni_gen/lib/src/tools/android_sdk_tools.dart
index 029555a..ed78566 100644
--- a/pkgs/jni_gen/lib/src/tools/android_sdk_tools.dart
+++ b/pkgs/jni_gen/lib/src/tools/android_sdk_tools.dart
@@ -63,10 +63,13 @@
   ///
   /// This function temporarily overwrites the build.gradle file by a stub with
   /// function to list all dependency paths for release variant.
-  /// This function fails if no gradle build is attempted
-  static List<String> getGradleClasspaths() {
-    stderr.writeln('trying to obtain gradle classpaths');
-    const android = 'android';
+  /// This function fails if no gradle build is attempted before.
+  ///
+  /// If current project is not directly buildable by gradle, eg: a plugin,
+  /// a relative path to other project can be specified using [androidProject].
+  static List<String> getGradleClasspaths([String androidProject = '.']) {
+    stderr.writeln('trying to obtain gradle classpaths...');
+    final android = join(androidProject, 'android');
     final buildGradle = join(android, 'build.gradle');
     final buildGradleOld = join(android, 'build.gradle.old');
     final origBuild = File(buildGradle);
@@ -80,8 +83,8 @@
     File(buildGradleOld).deleteSync();
     if (procRes.exitCode != 0) {
       throw Exception('\n\ngradle exited with exit code ${procRes.exitCode}\n'
-          'This can be related to a known issue with gradle. '
-          'Please run `flutter build apk` and try again\n');
+          'This can be related to a known issue with gradle. Please run '
+          '`flutter build apk` in $androidProject and try again\n');
     }
     final gradleClassPaths = (procRes.stdout as String).split('\n');
     if (gradleClassPaths.last.isEmpty) {