blob: 7b57158be1b78b59270dbe4d8c3744e8ce6b4131 [file] [log] [blame] [view] [edit]
## Implementing Java interfaces from Dart
Let's take a simple Java interface like `Runnable` that has a single `void`
method called `run`:
```java
// Java
public interface Runnable {
void run();
}
```
These are the bindings that JNIgen generates for this interface:
```dart
// Dart Bindings - Boilerplate omitted for clarity.
class Runnable extends JObject {
void run() { /* ... */ }
factory Runnable.implement($Runnable impl) { /* ... */ }
static void implementIn(
JImplementer implementer,
$Runnable impl,
) { /* ... */ }
}
abstract base mixin class $Runnable {
factory $Runnable({
required void Function() run,
bool run$async,
}) = _$Runnable;
bool get run$async => false;
void run();
}
class _$Runnable implements $Runnable {
_$Runnable({
required void Function() run,
this.run$async = false;
}) : _run = run;
final void Function() _run;
final bool run$async;
void run() {
return _run();
}
}
```
### Implementing interfaces inline
`Runnable` is used a lot to pass a void callback to a function. To simply this
workflow, Java 8 introduced lambdas.
```java
// Java
Runnable runnable = () -> System.out.println("hello");
```
To allow the same flexibility in Dart, `$Runnable` has a default factory that
simply gets each method of the interface as a closure argument. So you would do:
```dart
// Dart
final runnable = Runnable.implement($Runnable(run: () => print('hello')));
```
### Reuse the same implementation
The reason JNIgen generates the `$Runnable` class is to make it easier to reuse
the same implementation for multiple instances. This is analogous to actually
implementing the interface in Java instead of using the lambdas:
```java
// Java
public class Printer implements Runnable {
private final String text;
public Printer(String text) {
this.text = text;
}
@Override
public void run() {
System.out.println(text);
}
}
```
This way you can create multiple such Runnables like `new Printer("hello")` and
`new Printer("world")`.
You can do the same in Dart by creating a subclass that implements `$Runnable`:
```dart
// Dart
final class Printer with $Runnable {
final String text;
Printer(this.text);
@override
void run() {
print(text);
}
}
```
And similarly write `Runnable.implement(Printer('hello'))` and
`Runnable.implement(Printer('world'))`, to create multiple Runnables and share
common logic.
### Implement as a listener
By default, when any of methods of the implemented interface gets called, the
caller will block until the callee returns a result.
Void-returning functions don't have to return a result, so we can choose to not
block the caller when the method is just a listener. To signal this, pass `true`
to `<method name>$async` argument when implementing the interface inline:
```dart
// Dart
final runnable = Runnable.implement($Runnable(
run: () => print('hello'),
run$async: true, // This makes the run method non-blocking.
));
```
Similarly, when subclassing
```dart
// Dart
final class Printer with $Runnable {
final String text;
Printer(this.text);
@override
void run() {
print(text);
}
@override
bool get run$async => true; // This makes the run method non-blocking.
}
```
### Implement multiple interfaces
To implement more than one interface, use a `JImplementer` from `package:jni`.
`Closable` is another simple Java interface that has a single void `close`
method. Here is how we create an object that implements both `Runnable` and
`Closable`:
```dart
// Dart
final implementer = JImplementer();
Runnable.implementIn(implementer, $Runnable(run: () => print('run')));
Closable.implementIn(implementer, $Closable(close: () => print('close')));
final object = implementer.implement(Runnable.type); // or Closable.type.
```
As the created `object` implements both `Runnable` and `Closable`, it's also
possible to make it a `Closable` by passing in `Closable.type` to
`implementer.implement`. Or simply cast it after creation:
```dart
// Dart
final closable = object.as(Closable.type);
```