Let's say I have this high-level API in the framework:
abstract class Greeter {
void greet(String name);
}
This needs to use platform-specific APIs. Here's what the Windows implementation might look like:
// This type exists in the framework and uses FFI to the Windows embedder.
class WindowsGreeter implements Greeter {
@override
void greet(String name) {
final int engineId = PlatformDispatcher.instance.engineId!;
final Pointer<ffi.Utf8> namePtr = name.toNativeUtf8();
_greet(engineId, namePtr);
ffi.malloc.free(namePtr);
}
@Native<Void Function(Int64, Pointer<ffi.Utf8>)>(symbol: 'FlutterPrivateGreet')
external static void _greet(int engineId, Pointer<ffi.Utf8> name);
}
The embedder provides a C API for the framework's FFI.
For example, here's what that would look like in the Windows embedder:
extern "C" {
FLUTTER_EXPORT
void FlutterPrivateGreet(int64_t engine_id, const char* name);
} // extern "C"
This pattern supports out-of-tree embedders.
Let's say the Greeter
powers a widgets-layer feature. The WidgetsBinding
can expose a field for the Greeter
implementation:
mixin WidgetsBinding ... {
@override
void initInstances() {
if (Platform.isWindows) {
greeter = WindowsGreeter();
}
}
late Greeter greeter;
}
At startup, the out-of-tree embedder registers its custom Greeter
implementation with the framework:
void main() {
WidgetsFlutterBinding.ensureInitialized();
if (isOutOfTreeEmbedder) {
WidgetsBinding.instance.greeter = OutOfTreeGreeter();
}
runApp(...);
}
This is the same pattern that the framework uses for the PlatformMenuDelegate
.