| // 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 'dart:ui'; | |
| import 'package:flutter/material.dart'; | |
| import 'package:jni/jni.dart'; | |
| import 'package:jni_flutter/jni_flutter.dart'; | |
| double randomDouble() { | |
| final math = JClass.forName("java/lang/Math"); | |
| final random = | |
| math.staticMethodId("random", "()D").call(math, jdouble.type, []); | |
| math.release(); | |
| return random; | |
| } | |
| int uptime() { | |
| return JClass.forName("android/os/SystemClock").use( | |
| (systemClock) => systemClock | |
| .staticMethodId("uptimeMillis", "()J") | |
| .call(systemClock, jlong.type, []), | |
| ); | |
| } | |
| String backAndForth() { | |
| final jstring = '🪓'.toJString(); | |
| final dartString = jstring.toDartString(releaseOriginal: true); | |
| return dartString; | |
| } | |
| void quit() { | |
| final activity = androidActivity(PlatformDispatcher.instance.engineId!); | |
| if (activity == null) return; | |
| activity.jClass | |
| .instanceMethodId("finish", "()V") | |
| .call(activity, jvoid.type, []); | |
| activity.release(); | |
| } | |
| void showToast(String text) { | |
| final activity = 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 applicationContext = androidApplicationContext; | |
| 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(); | |
| } | |
| final examples = [ | |
| Example("Math.random()", () => randomDouble(), runInitially: false), | |
| if (Platform.isAndroid) ...[ | |
| Example("Minutes of usage since reboot", | |
| () => (uptime() / (60 * 1000)).floor()), | |
| Example("Back and forth string conversion", () => backAndForth()), | |
| Example("Device name", () { | |
| final buildClass = JClass.forName("android/os/Build"); | |
| return buildClass | |
| .staticFieldId("DEVICE", JString.type.signature) | |
| .get(buildClass, JString.type) | |
| .toDartString(releaseOriginal: true); | |
| }), | |
| Example( | |
| "Package name", | |
| () { | |
| final activity = | |
| 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( | |
| "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 StatelessWidget { | |
| const MyApp(this.examples, {super.key}); | |
| final List<Example> examples; | |
| @override | |
| Widget build(BuildContext context) { | |
| return MaterialApp( | |
| home: Scaffold( | |
| appBar: AppBar( | |
| title: const Text('JNI Examples'), | |
| ), | |
| body: ListView.builder( | |
| itemCount: examples.length, | |
| itemBuilder: (context, i) { | |
| final eg = examples[i]; | |
| return ExampleCard(eg); | |
| }, | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class ExampleCard extends StatefulWidget { | |
| const ExampleCard(this.example, {super.key}); | |
| final Example example; | |
| @override | |
| State<ExampleCard> 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), | |
| ]), | |
| ); | |
| } | |
| } |