blob: c21802b925d720f487973675e2d2509220519802 [file]
// ignore_for_file: library_private_types_in_public_api
import 'package:flutter/material.dart';
import 'dart:io';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:jni/jni.dart';
import 'package:jni/jni_object.dart';
late Jni jni;
String localToJavaString(int n) {
final jniEnv = jni.getEnv();
final arena = Arena();
final cls = jniEnv.FindClass("java/lang/String".toNativeChars(arena));
final mId = jniEnv.GetStaticMethodID(cls, "valueOf".toNativeChars(),
"(I)Ljava/lang/String;".toNativeChars(arena));
final i = arena<JValue>();
i.ref.i = n;
final res = jniEnv.CallStaticObjectMethodA(cls, mId, i);
final str = jniEnv.asDartString(res);
jniEnv.deleteAllLocalRefs([res, cls]);
arena.releaseAll();
return str;
}
int random(int n) {
final arena = Arena();
final jniEnv = jni.getEnv();
final randomCls = jniEnv.FindClass("java/util/Random".toNativeChars(arena));
final ctor = jniEnv.GetMethodID(
randomCls, "<init>".toNativeChars(arena), "()V".toNativeChars(arena));
final random = jniEnv.NewObject(randomCls, ctor);
final nextInt = jniEnv.GetMethodID(
randomCls, "nextInt".toNativeChars(arena), "(I)I".toNativeChars(arena));
final res = jniEnv.CallIntMethodA(random, nextInt, Jni.jvalues([n]));
jniEnv.deleteAllLocalRefs([randomCls, random]);
return res;
}
double randomDouble() {
final math = jni.findJniClass("java/lang/Math");
final random = math.callStaticDoubleMethodByName("random", "()D", []);
math.delete();
return random;
}
int uptime() {
final systemClock = jni.findJniClass("android/os/SystemClock");
final uptime =
systemClock.callStaticLongMethodByName("uptimeMillis", "()J", []);
systemClock.delete();
return uptime;
}
void quit() {
jni
.wrap(jni.getCurrentActivity())
.use((ac) => ac.callVoidMethodByName("finish", "()V", []));
}
void showToast(String text) {
// This is example for calling you app's custom java code.
// You place the AnyToast class in you app's android/ source
// Folder, with a Keep annotation or appropriate proguard rules
// to retain the class in release mode.
// In this example, AnyToast class is just a type of `Toast` that
// can be called from any thread. See
// android/app/src/main/java/dev/dart/jni_example/AnyToast.java
jni.invokeObjectMethod(
"dev/dart/jni_example/AnyToast",
"makeText",
"(Landroid/app/Activity;Landroid/content/Context;"
"Ljava/lang/CharSequence;I)"
"Ldev/dart/jni_example/AnyToast;",
[
jni.getCurrentActivity(),
jni.getCachedApplicationContext(),
":-)",
0
]).callVoidMethodByName("show", "()V", []);
}
void main() {
if (!Platform.isAndroid) {
Jni.spawn();
}
jni = Jni.getInstance();
final examples = [
Example("String.valueOf(1332)", () => localToJavaString(1332)),
Example("Generate random number", () => random(180), runInitially: false),
Example("Math.random()", () => randomDouble(), runInitially: false),
if (Platform.isAndroid) ...[
Example("Minutes of usage since reboot",
() => (uptime() / (60 * 1000)).floor()),
Example(
"Device name",
() => jni.retrieveStringField(
"android/os/Build", "DEVICE", "Ljava/lang/String;")),
Example(
"Package name",
() => jni.wrap(jni.getCurrentActivity()).use((activity) => activity
.callStringMethodByName(
"getPackageName", "()Ljava/lang/String;", [])),
),
Example("Show toast", () => showToast("Hello from JNI!"),
runInitially: false),
Example(
"Quit",
quit,
runInitially: false,
),
]
];
runApp(MyApp(examples));
}
class Example {
String title;
dynamic Function() callback;
bool runInitially;
Example(this.title, this.callback, {this.runInitially = true});
}
class MyApp extends StatefulWidget {
const MyApp(this.examples, {Key? key}) : super(key: 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(
home: Scaffold(
appBar: AppBar(
title: const Text('JNI Examples'),
),
body: ListView.builder(
itemCount: widget.examples.length,
itemBuilder: (context, i) {
final eg = widget.examples[i];
return ExampleCard(eg);
}),
),
);
}
}
class ExampleCard extends StatefulWidget {
const ExampleCard(this.example, {Key? key}) : super(key: key);
final Example example;
@override
_ExampleCardState createState() => _ExampleCardState();
}
class _ExampleCardState extends State<ExampleCard> {
Widget _pad(Widget w, double h, double v) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: h, vertical: v), child: w);
}
bool _run = false;
@override
void initState() {
super.initState();
_run = widget.example.runInitially;
}
@override
Widget build(BuildContext context) {
final eg = widget.example;
var result = "";
var hasError = false;
if (_run) {
try {
result = eg.callback().toString();
} on Exception catch (e) {
hasError = true;
result = e.toString();
} on Error catch (e) {
hasError = true;
result = e.toString();
}
}
var resultStyle = const TextStyle(fontFamily: "Monospace");
if (hasError) {
resultStyle = const TextStyle(fontFamily: "Monospace", color: Colors.red);
}
return Card(
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(eg.title,
softWrap: true,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
_pad(
Text(result.toString(), softWrap: true, style: resultStyle), 8, 16),
_pad(
ElevatedButton(
child: Text(_run ? "Run again" : "Run"),
onPressed: () => setState(() => _run = true),
),
8,
8),
]),
);
}
}