diff --git a/pkgs/jnigen/example/in_app_java/README.md b/pkgs/jnigen/example/in_app_java/README.md index 5c4da0a3d6..34cb743206 100644 --- a/pkgs/jnigen/example/in_app_java/README.md +++ b/pkgs/jnigen/example/in_app_java/README.md @@ -1,28 +1,31 @@ # In-App Java Example This example shows how to write custom java code in `android/app/src` and call -it using `jnigen` generated bindings. +it using bindings generated by `jnigen`. -#### How to run this example: +### How to run this example: - Run `flutter run` to run the app. - To regenerate bindings after changing Java code, run - `flutter pub run jnigen --config jnigen.yaml`. This requires at least one APK - build to have been run before, so that it's possible for `jnigen` to obtain - classpaths of Android Gradle libraries. Therefore, once run - `flutter build apk` before generating bindings for the first time, or after a - `flutter clean`. + `dart run tool/jnigen.dart`. This requires at least one APK build to have + been run before, so that `jnigen` can obtain classpaths of Android Gradle + libraries. Therefore, run `flutter build apk` once before generating bindings + for the first time, or after a `flutter clean`. -#### General steps +### General steps -These are general steps to integrate Java code into a flutter project using +These are general steps to integrate Java code into a Flutter project using `jnigen`. -- Write Java code in suitable package folder, under `android/` subproject of the - flutter app. -- Create A jnigen config like `jnigen.yaml` in this example. -- Generate bindings using jnigen config. -- Add proguard rules to exclude your custom classes from tree shaking, since - they are always accessed reflectively in JNI. +- Place Java code in a suitable package folder under the `android/` subproject + of the flutter app. +- Create a `jnigen` configuration script like `tool/jnigen.dart` in this + example. +- Generate bindings by running that script with `dart run`. +- To prevent tree shaking of your custom classes (which are always accessed + reflectively in JNI) use either the `@Keep` annotation + (`androidx.annotation.Keep`) for code written in the application itself, or + a [proguard-rules file](https://github.com/dart-lang/native/blob/main/pkgs/jnigen/example/in_app_java/android/app/proguard-rules.pro) + for external libraries. - Build and run the app. diff --git a/pkgs/jnigen/example/in_app_java/jnigen.yaml b/pkgs/jnigen/example/in_app_java/jnigen.yaml deleted file mode 100644 index 20b2636e00..0000000000 --- a/pkgs/jnigen/example/in_app_java/jnigen.yaml +++ /dev/null @@ -1,16 +0,0 @@ -android_sdk_config: - add_gradle_deps: true - -output: - dart: - path: lib/android_utils.dart - structure: single_file - -source_path: - - 'android/app/src/main/java' -classes: - - 'com.example.in_app_java' # Generate the entire package - - 'androidx.emoji2.text.EmojiCompat' # From gradle's compile classpath - - 'androidx.emoji2.text.DefaultEmojiCompatConfig' # From gradle's compile classpath - - 'android.os.Build' # from gradle's compile classpath - - 'java.util.HashMap' # from gradle's compile classpath diff --git a/pkgs/jnigen/example/in_app_java/lib/android_utils.dart b/pkgs/jnigen/example/in_app_java/lib/android_utils.g.dart similarity index 100% rename from pkgs/jnigen/example/in_app_java/lib/android_utils.dart rename to pkgs/jnigen/example/in_app_java/lib/android_utils.g.dart diff --git a/pkgs/jnigen/example/in_app_java/lib/main.dart b/pkgs/jnigen/example/in_app_java/lib/main.dart index 520a0aa972..adab59c7d4 100644 --- a/pkgs/jnigen/example/in_app_java/lib/main.dart +++ b/pkgs/jnigen/example/in_app_java/lib/main.dart @@ -7,7 +7,7 @@ import 'package:jni/jni.dart'; // The hierarchy created in generated code will mirror the java package // structure. -import 'android_utils.dart'; +import 'android_utils.g.dart'; JObject activity = JObject.fromReference(Jni.getCurrentActivity()); JObject context = JObject.fromReference(Jni.getCachedApplicationContext()); diff --git a/pkgs/jnigen/example/in_app_java/pubspec.yaml b/pkgs/jnigen/example/in_app_java/pubspec.yaml index e09b6dc219..601e41e76d 100644 --- a/pkgs/jnigen/example/in_app_java/pubspec.yaml +++ b/pkgs/jnigen/example/in_app_java/pubspec.yaml @@ -15,9 +15,6 @@ dependencies: sdk: flutter 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: @@ -29,39 +26,5 @@ dev_dependencies: flutter_lints: ^6.0.0 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/jnigen/example/in_app_java/tool/jnigen.dart b/pkgs/jnigen/example/in_app_java/tool/jnigen.dart new file mode 100644 index 0000000000..8c74220813 --- /dev/null +++ b/pkgs/jnigen/example/in_app_java/tool/jnigen.dart @@ -0,0 +1,26 @@ +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/android_utils.g.dart'), + structure: OutputStructure.singleFile, + ), + ), + androidSdkConfig: AndroidSdkConfig(addGradleDeps: true), + sourcePath: [packageRoot.resolve('android/app/src/main/java')], + classes: [ + 'com.example.in_app_java', // Generate the entire package + 'androidx.emoji2.text.EmojiCompat', // From gradle's compile classpath + 'androidx.emoji2.text.DefaultEmojiCompatConfig', // From gradle's compile classpath + 'android.os.Build', // from gradle's compile classpath + 'java.util.HashMap', // from gradle's compile classpath + ], + ), + ); +} diff --git a/pkgs/jnigen/test/regenerate_examples_test.dart b/pkgs/jnigen/test/regenerate_examples_test.dart index 8cbb7db72a..181d1055b1 100644 --- a/pkgs/jnigen/test/regenerate_examples_test.dart +++ b/pkgs/jnigen/test/regenerate_examples_test.dart @@ -14,10 +14,10 @@ import 'test_util/test_util.dart'; /// Generates bindings using jnigen config in [exampleName] and compares /// them to provided reference outputs. /// -/// [dartOutput] and [cOutput] are relative paths from example project dir. +/// [dartOutput] is a relative path from the example project dir. /// /// Pass [isLargeTest] as true if the test will take considerable time. -void testExample(String exampleName, String dartOutput, String? cOutput, +void testExample(String exampleName, String dartOutput, {bool isLargeTest = false}) { test( 'Generate and compare bindings for $exampleName', @@ -37,30 +37,65 @@ void testExample(String exampleName, String dartOutput, String? cOutput, ); } +void testDartApiExample( + {required String exampleName, + required String generatorScriptPath, + required String outputPath, + bool isLargeTest = false}) { + test( + 'Generate and compare bindings for $exampleName', + timeout: const Timeout.factor(3), + () async { + final examplePath = join('example', exampleName); + try { + final generatorResult = await Process.run( + Platform.resolvedExecutable, ['run', generatorScriptPath], + workingDirectory: examplePath); + if ((generatorResult.stderr as String) + .contains('Gradle execution failed.')) { + stderr.writeln('Skip: $exampleName'); + return; + } + final processResults = await Process.run( + 'git', ['diff', '--exit-code', outputPath], + workingDirectory: examplePath); + if (processResults.exitCode == 1) { + fail('The checked-in bindings of $exampleName are out of date. Run ' + 'the generator script ($generatorScriptPath) and commit the ' + 'changes.\n\n${processResults.stdout}'); + } else if (processResults.exitCode != 0) { + throw Exception( + 'Invocation of "git diff" failed:\n${processResults.stderr}'); + } + } on GradleException catch (_) { + stderr.writeln('Skip: $exampleName'); + } + }, + tags: isLargeTest ? largeTestTag : null, + ); +} + void main() async { await checkLocallyBuiltDependencies(); - testExample( - 'in_app_java', - join('lib', 'android_utils.dart'), - join('src', 'android_utils'), + testDartApiExample( + exampleName: 'in_app_java', + generatorScriptPath: 'tool/jnigen.dart', + outputPath: join('lib', 'android_utils.g.dart'), isLargeTest: true, ); testExample( 'pdfbox_plugin', join('lib', 'src', 'third_party'), - 'src', isLargeTest: false, ); testExample( 'notification_plugin', join('lib', 'notifications.dart'), - 'src', isLargeTest: true, ); testExample( 'kotlin_plugin', join('lib', 'kotlin_bindings.dart'), - 'src', isLargeTest: true, ); }