blob: 59da51bc406193c36343a08ea396c6cc8e5bae17 [file] [log] [blame] [view]
# Dealing with OS differences
This doc explains how to deal with differences between iOS and macOS APIs, and
differences between API versions.
## iOS/macOS API differences
Objective-C can guard iOS/macOS specific code with `#if` macros, so that it
will only be compiled on specific platforms:
```obj-c
#if TARGET_OS_IPHONE
@interface WKWebView : UIView
#else
@interface WKWebView : NSView
#endif
```
Dart 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.
```dart
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.
```dart
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:
```dart
// 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) {
// ...
}
}
```
## API version differences
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.
```obj-c
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`:
```dart
// 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.