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
21 changes: 13 additions & 8 deletions .github/workflows/jnigen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,6 @@ jobs:
distribution: 'zulu'
java-version: '17'
cache: maven
## Committed bindings are formatted with clang-format.
## So this is required to format generated bindings identically
- name: install clang tools
run: |
sudo apt-get update -y
sudo apt-get install -y clang-format
- name: Install dependencies
run: dart pub get
- name: build in_app_java APK
Expand Down Expand Up @@ -172,8 +166,11 @@ jobs:
working-directory: ./pkgs/jni
- name: install clang tools & CMake
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20
sudo apt-get update -y
sudo apt-get install -y clang-format build-essential cmake
sudo apt-get install -y clang-format-20 build-essential cmake
- run: flutter pub get
- name: Check formatting
run: dart format --output=none --set-exit-if-changed .
Expand Down Expand Up @@ -224,6 +221,14 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel: true
path-to-lcov: ./pkgs/jni/coverage/lcov.info
- name: building the example project succeeds
working-directory: ./pkgs/jni/example/
run: |
flutter build apk
- name: regenerate & compare jnigen bindings
run: |
dart run tool/generate_jni_bindings.dart
git diff --exit-code -- lib/src/plugin
# TODO(https://github.com/dart-lang/ffigen/issues/555): FFIgen generated
# on my machine has macOS specific stuff and CI does not.
# We should just generate the struct as opaque, but we currently can't.
Expand Down Expand Up @@ -419,7 +424,7 @@ jobs:
java-version: '17'
- run: |
sudo apt-get update -y
sudo apt-get install -y ninja-build libgtk-3-dev clang-format
sudo apt-get install -y ninja-build libgtk-3-dev
- run: flutter config --enable-linux-desktop
- run: dart pub get
- name: Generate full bindings
Expand Down
15 changes: 10 additions & 5 deletions pkgs/jni/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
- **Breaking Change**: Made `Jni.env` internal.
- **Breaking Change**: Renamed `JObjType` to `JType`.
- **Breaking Change**: Made all of the type classes internal.
- **Breaking Change**: Removed `Jni.getApplicationClassLoader()`,
`Jni.getCurrentActivity()`, and `Jni.getCachedApplicationContext()`. Instead
use `Jni.androidApplicationContext(engineId)` to access the application
context and use `Jni.androidActivity(engineId)` to acccess the activity.
- Update to the latest lints.

## 0.14.2
Expand All @@ -13,12 +17,13 @@

## 0.14.1

- Updated `bin/setup.dart` to use Gradle instead of Maven for building Java sources. Added gradle executables
and bootstrap jars [#2003](https://github.com/dart-lang/native/issues/2003)
- Added `JObject.isInstanceOf` which checks whether a `JObject` is an instance
- Updated `bin/setup.dart` to use Gradle instead of Maven for building Java
sources. Added gradle executables and bootstrap jars
[#2003](https://github.com/dart-lang/native/issues/2003)
- Added `JObject.isInstanceOf` which checks whether a `JObject` is an instance
of a java class.
- Fixed a [bug](https://github.com/dart-lang/native/issues/1908) where
Java interfaces implemented in on the main thread in Dart could deadlock when
- Fixed a [bug](https://github.com/dart-lang/native/issues/1908) where Java
interfaces implemented in on the main thread in Dart could deadlock when
invoked from the main thread outside the context of a Dart isolate.

## 0.14.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,48 +7,72 @@
import android.app.Activity;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

public class JniPlugin implements FlutterPlugin, ActivityAware {
private static final ConcurrentHashMap<Long, JniPlugin> pluginMap = new ConcurrentHashMap<>();

private long engineId;
private volatile Context context;
private volatile Activity activity;

public static @NonNull Context getApplicationContext(long engineId) {
return Objects.requireNonNull(pluginMap.get(engineId)).context;
}

public static @Nullable Activity getActivity(long engineId) {
return Objects.requireNonNull(pluginMap.get(engineId)).activity;
}

@Override
@SuppressWarnings("deprecation")
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
setup(binding.getApplicationContext());
//noinspection deprecation
engineId = binding.getFlutterEngine().getEngineId();
context = binding.getApplicationContext();
pluginMap.put(engineId, this);
}

private void setup(Context context) {
initializeJni(context, getClass().getClassLoader());
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
context = null;
activity = null;
pluginMap.remove(engineId);
}

@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {}
private void setActivity(Activity newActivity) {
activity = newActivity;
}

// Activity handling methods
@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
Activity activity = binding.getActivity();
setJniActivity(activity, activity.getApplicationContext());
setActivity(binding.getActivity());
}

@Override
public void onDetachedFromActivityForConfigChanges() {}
public void onDetachedFromActivityForConfigChanges() {
setActivity(null);
}

@Override
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
Activity activity = binding.getActivity();
setJniActivity(activity, activity.getApplicationContext());
setActivity(binding.getActivity());
}

@Override
public void onDetachedFromActivity() {}

native void initializeJni(Context context, ClassLoader classLoader);
public void onDetachedFromActivity() {
setActivity(null);
}

native void setJniActivity(Activity activity, Context context);
static native void setClassLoader(ClassLoader classLoader);

static {
System.loadLibrary("dartjni");
setClassLoader(JniPlugin.class.getClassLoader());
}
}
2 changes: 1 addition & 1 deletion pkgs/jni/example/android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.6.0" apply false
id "org.jetbrains.kotlin.android" version "1.8.10" apply false
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
}

include ":app"
77 changes: 42 additions & 35 deletions pkgs/jni/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// ignore_for_file: library_private_types_in_public_api

import 'dart:io';
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:jni/jni.dart';
Expand Down Expand Up @@ -32,36 +33,42 @@ String backAndForth() {
}

void quit() {
JObject.fromReference(Jni.getCurrentActivity()).use((ac) =>
ac.jClass.instanceMethodId("finish", "()V").call(ac, jvoid.type, []));
final activity = Jni.androidActivity(PlatformDispatcher.instance.engineId!);
if (activity == null) return;
activity.jClass
.instanceMethodId("finish", "()V")
.call(activity, jvoid.type, []);
activity.release();
}

void showToast(String text) {
// This is example for calling your app's custom java code.
// Place the Toaster class in the app's android/ source Folder, with a Keep
// annotation or appropriate proguard rules to retain classes in release mode.
//
// In this example, Toaster class wraps android.widget.Toast so that it
// can be called from any thread. See
// android/app/src/main/java/com/github/dart_lang/jni_example/Toaster.java
final activity = Jni.androidActivity(PlatformDispatcher.instance.engineId!);
if (activity == null) return;
final toasterClass =
JClass.forName('com/github/dart_lang/jni_example/Toaster');
final makeText = toasterClass.staticMethodId(
'makeText',
'(Landroid/app/Activity;Landroid/content/Context;'
'Ljava/lang/CharSequence;I)'
'Lcom/github/dart_lang/jni_example/Toaster;');
final toaster = makeText.call(toasterClass, JObject.type, [
Jni.getCurrentActivity(),
Jni.getCachedApplicationContext(),
'😀'.toJString(),
final applicationContext =
Jni.androidApplicationContext(PlatformDispatcher.instance.engineId!);
final toaster = makeText(toasterClass, JObject.type, [
activity,
applicationContext,
text.toJString(),
0,
]);
final show = toasterClass.instanceMethodId('show', '()V');
show(toaster, jvoid.type, []);
toaster.release();
applicationContext.release();
activity.release();
text.toJString().release();
}

void main() {
WidgetsFlutterBinding.ensureInitialized();
if (!Platform.isAndroid) {
Jni.spawn();
}
Expand All @@ -80,13 +87,22 @@ void main() {
}),
Example(
"Package name",
() => JObject.fromReference(Jni.getCurrentActivity()).use((activity) =>
activity.jClass
.instanceMethodId("getPackageName", "()Ljava/lang/String;")
.call(activity, JString.type, [])),
() {
final activity =
Jni.androidActivity(PlatformDispatcher.instance.engineId!);
if (activity == null) return "Activity not available";
final packageName = activity.jClass
.instanceMethodId("getPackageName", "()Ljava/lang/String;")
.call(activity, JString.type, []);
activity.release();
return packageName;
},
),
Example(
"Show toast",
() => showToast("Hello from JNI!"),
runInitially: false,
),
Example("Show toast", () => showToast("Hello from JNI!"),
runInitially: false),
Example(
"Quit",
quit,
Expand All @@ -104,20 +120,10 @@ class Example {
Example(this.title, this.callback, {this.runInitially = true});
}

class MyApp extends StatefulWidget {
class MyApp extends StatelessWidget {
const MyApp(this.examples, {super.key});
final List<Example> examples;

@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
}

@override
Widget build(BuildContext context) {
return MaterialApp(
Expand All @@ -126,11 +132,12 @@ class _MyAppState extends State<MyApp> {
title: const Text('JNI Examples'),
),
body: ListView.builder(
itemCount: widget.examples.length,
itemBuilder: (context, i) {
final eg = widget.examples[i];
return ExampleCard(eg);
}),
itemCount: examples.length,
itemBuilder: (context, i) {
final eg = examples[i];
return ExampleCard(eg);
},
),
),
);
}
Expand Down
Loading