This doc explains how to deal with differences between iOS and macOS APIs, and differences between API versions.
Objective-C can guard iOS/macOS specific code with #if macros, so that it
will only be compiled on specific platforms:
#if TARGET_OS_IPHONE
@interface WKWebView : UIView
#else
@interface WKWebView : NSView
#endifDart has no equivalent of this, because Dart's kernel files are designed to
work on any platform. Platform.isMacOS etc are runtime values, not compile
time constants (technical detail: they're compile time constant in AOT mode,
but not in JIT mode). So it's not possible to conditionally import dart files
based on Platform.isMacOS/isIOS.
There are two approaches for dealing with platform differences, depending
on how significant the differences are. If the API you're working with only
has small differences between iOS and macOS, you can try to generate a
single FFIgen wrapper for both, and use runtime Platform.isMacOS/isIOS
checks to call different methods. At runtime, FFIgen lazy loads all classes
and methods, so if a class/method doesn't exist in your plugin/app's native
code, that's fine as long as the class/method isn't used at runtime.
final foo = Foo();
if (Platform.isMacOS) {
final bar = MacSpecificBar();
foo.macSpecificMethod(bar);
} else {
assert(Platform.isIOS);
final bar = IosSpecificBar();
foo.iosSpecificMethod(bar);
}If the API differences are severe enough that they would cause compile time
errors if they're in a single library, that approach won't work. For example,
WKWebView inherits from UIView on iOS and NSView on macOS, so there's
no way FFIgen can generate a single WKWebView class for both platforms.
In cases like this, it's necessary to run FFIgen separately for each
platform, and generate different bindings for iOS and macOS. Since you can't
conditionally import based on the OS, you need a way of pulling both sets
of bindings into your plugin. The simplest approach is to use the rename
config option to rename the APIs so they don't conflict (eg WKWebViewIOS
and WKWebViewMacOS), then import both sets of bindings and use Platform
checks to call different APIs.
if (Platform.isMacOS) {
final webView = WKWebViewMacOS();
// ...
} else {
assert(Platform.isIOS);
final webView = WKWebViewIOS();
// ...
}If renaming isn't practical (e.g. there would be too many renames or Platform
checks), then you can write separate Dart classes for each platform that
implement a shared interface:
// Both imports were generated by FFIgen.
import 'web_view_bindings_mac.dart' as mac;
import 'web_view_bindings_ios.dart' as ios;
abstract interface class WebView {
void load(Uri uri);
factory WebView() {
if (Platform.isMacOS) {
return WebViewMac();
} else {
assert(Platform.isIOS);
return WebViewIOS();
}
}
}
class WebViewMac implements WebView {
mac.WKWebView _view;
@override
void load(Uri uri) {
// ...
}
}
class WebViewIOS implements WebView {
ios.WKWebView _view;
@override
void load(Uri uri) {
// ...
}
}Objective-C uses the @available annotation to allow developers to write if
statements that do different things on different OS versions. It also generates
a compiler warning if the developer uses an API that is only available in
particular OS versions, without guarding it with an @available if statement.
if (@available(iOS 18, *)) {
// Use newer iOS 18 API.
} else {
// Fallback to old API.
}We can't replicate the compiler warning in Dart/FFIgen at the moment, but we
can write the runtime version check. The recommended way of doing this is using
package:objective_c's checkOsVersion:
// If you only need to support iOS:
if (checkOsVersion(iOS: Version(18, 0, 0))) {
// Use newer iOS 18 API.
} else {
// Fallback to old API.
}
// If you need to support iOS and macOS:
if (checkOsVersion(iOS: Version(18, 0, 0), macOS: Version(15, 3, 0))) {
// Use newer API available in iOS 18 and macOS 15.3.
} else {
// Fallback to old API.
}checkOsVersion returns true if the current OS version is equal to or greater
than the given version.
FFIgen's generated code includes version checks that will throw an
OsVersionError if the API is not available in the current OS version. But it's
better to write if statements like above, rather than trying to catch this
error.