Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 18 additions & 15 deletions pkgs/jnigen/example/in_app_java/README.md
Original file line number Diff line number Diff line change
@@ -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.
16 changes: 0 additions & 16 deletions pkgs/jnigen/example/in_app_java/jnigen.yaml

This file was deleted.

2 changes: 1 addition & 1 deletion pkgs/jnigen/example/in_app_java/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
37 changes: 0 additions & 37 deletions pkgs/jnigen/example/in_app_java/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
26 changes: 26 additions & 0 deletions pkgs/jnigen/example/in_app_java/tool/jnigen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'dart:io';

import 'package:jnigen/jnigen.dart';

void main(List<String> 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),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How often do we pass this or not? Are we passing this to 90% of our use cases? If yes, should this be the default? And if it's the default, does that require there always is a Gradle project configured? How is this Gradle project configured, always via flutter create?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You either can download and pass the jars or use this option, I don't have data to say 90% or 80%.

Always via flutter create, yes.

sourcePath: [packageRoot.resolve('android/app/src/main/java')],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: sourcePath is singular, but the type is a list.

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
],
),
);
}
53 changes: 44 additions & 9 deletions pkgs/jnigen/test/regenerate_examples_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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,
);
}