One of the main selling-points of Flutter is the ability to run one app on multiple platforms. In general, we use the same code on all platforms, but occasionally there does come a need to write some custom platform-specific code.
Before writing any platform-specific code, always check for a package on pub, someone has probably already done the work.
Pigeon
Use the assistance of the Pigeon package to help with the Dart to platform communication. Pigeon is used as a dev-dependency.
Messages file
The messages
file contains Pigeon configuration, Api, and data-model definitions. Pigeon will use this file to generate the appropriate Dart and platform code.
Pigeon configuration gives Pigeon information about how to generate the code and where to save the output.
Api definitions are a combination of host-Api and Flutter-Api definitions. Host-Api definitions are implemented by the platform (Flutter code calls the host-Api). Flutter-Api definitions are implemented in Flutter (platform code calls the Flutter-Api).
In the generated Dart code, the host-Api functions always return a Future
. If a function in Flutter or in platform code needs to perform a long-running task, it needs to be annotated with @async
.
Data-model definitions are simple data classes that define what data is sent between Dart and the platform. The supported data types can be seen here. Data-model definition classes must contain only properties (functions or constructors will not be present in the generated code).
import 'package:pigeon/pigeon.dart';
// pigeon configuration
void configurePigeon(PigeonOptions options) {
options
..dartOut = './lib/brightness_pigeon.dart'
..javaOut = './android/app/src/main/java/io/flutter/plugins/brightness/BrightnessPigeon.java'
..javaOptions.package = 'io.flutter.plugins.brightness'
..objcHeaderOut = 'ios/Runner/BrightnessPigeon.h'
..objcSourceOut = 'ios/Runner/BrightnessPigeon.m';
}
// host-Api definition
@HostApi()
abstract class DeviceBrightnessApi {
BrightnessConstraintsMessage getBrightnessConstraints();
@async
BrightnessMessage getDeviceBrightness();
void setDeviceBrightness(BrightnessMessage brightness);
}
// Flutter-Api definition
@FlutterApi()
abstract class FlutterBrightnessApi {
BrightnessMessage getDefaultDeviceBrightness();
@async
BrightnessMessage getPreferredDeviceBrightness();
void onDeviceBrightnessChanged(BrightnessMessage brightness);
}
// data-model definition
class BrightnessConstraintsMessage {
int maxBrightness;
int minBrightness;
}
class BrightnessMessage {
int brightnessValue;
}
Pigeon invocation
Run the Pigeon invocation to create Dart and platform code from the messages file. The invocation can accept several arguments to configure how the code is generated and where to save the output.
flutter pub run pigeon --input pigeons/brightness_messages.dart
Dart
Use the generated Dart code to communicate with the platform.
// calling the host-Api
final api = DeviceBrightnessApi();
final brightnessConstraintsMessage = await api.getBrightnessConstraints();
final brightnessMessage = await api.getDeviceBrightness();
final newBrightness = BrightnessMessage()..brightnessValue = 50;
await api.setDeviceBrightness(newBrightness);
// setting-up the Flutter-Api
void main() {
WidgetsFlutterBinding.ensureInitialized();
FlutterBrightness();
runApp(MyApp());
}
// implementing the Flutter-Api
class FlutterBrightness implements FlutterBrightnessApi {
FlutterBrightness() {
FlutterBrightnessApi.setup(this);
}
@override
BrightnessMessage getDefaultDeviceBrightness() {
// ...
}
@override
Future<BrightnessMessage> getPreferredDeviceBrightness() async {
// ...
}
@override
void onDeviceBrightnessChanged(BrightnessMessage arg) {
// ...
}
}
In some cases, a mapper is needed to transform between app-data-models and generated message-data-models. Note that message-data-models do not have constructors.
// mapping from message to app-data-model
class BrightnessMapper {
BrightnessMapper._();
static Brightness map(BrightnessMessage message) {
return Brightness(
value: message.brightnessValue,
);
}
}
// mapping from app-data-model to message
class BrightnessMessageMapper {
BrightnessMessageMapper._();
static BrightnessMessage map(Brightness brightness) {
return BrightnessMessage()
..brightnessValue = brightness.value;
}
}
Android
Pigeon generates Java code for Android. This code is accessible to Kotlin.
// setup
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// setting-up the host-Api
BrightnessPigeon.DeviceBrightnessApi.setup(flutterEngine.dartExecutor.binaryMessenger, DeviceBrightness())
// setting-up the Flutter-Api
val flutterApi = BrightnessPigeon.FlutterBrightnessApi(flutterEngine.dartExecutor.binaryMessenger)
}
}
// implementing the host-Api
private class DeviceBrightness: BrightnessPigeon.DeviceBrightnessApi {
override fun getBrightnessConstraints(): BrightnessPigeon.BrightnessConstraintsMessage {
val message = BrightnessPigeon.BrightnessConstraintsMessage()
message.minBrightness = 0
message.maxBrightness = 100
return message
}
override fun getDeviceBrightness(result: BrightnessPigeon.Result<BrightnessPigeon.BrightnessMessage>?) {
val message = BrightnessPigeon.BrightnessMessage()
message.brightnessValue = 50 // long-running operation to obtain the brightness
result!!.success(message)
}
override fun setDeviceBrightness(arg: BrightnessPigeon.BrightnessMessage?) {
// ...
}
}
// calling the Flutter-Api (the Flutter-Api implementation must be instantiated on the Dart side)
flutterApi.getDefaultDeviceBrightness { brightnessMessage ->
// ...
}
flutterApi.getPreferredDeviceBrightness { brightnessMessage ->
// ...
}
val message = BrightnessPigeon.BrightnessMessage()
message.brightnessValue = 50
flutterApi.onDeviceBrightnessChanged(message){
// ...
}
iOS
Pigeon generates Objective-C code for iOS. This code is accessible to Swift.
Drag the generate Objective-C files into the Xcode project to import them.
// making Objective-C code available to Swift (Runner-Bridging-Header.h)
#import "brightness_pigeon.h"
// setup
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
let controller = window.rootViewController as! FlutterViewController
// setting-up the host-Api
let brightness = Brightness()
DeviceBrightnessApiSetup(controller.binaryMessenger, brightness)
// setting-up the Flutter-Api
let flutterApi = FlutterBrightnessApi(binaryMessenger: controller.binaryMessenger)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
// implementing the host-Api
class Brightness: DeviceBrightnessApi {
func getBrightnessConstraints(_ error: AutoreleasingUnsafeMutablePointer<FlutterError?>) -> BrightnessConstraintsMessage? {
let message = BrightnessConstraintsMessage()
message.minBrightness = 0
message.maxBrightness = 100
return message
}
func getDeviceBrightness(_ completion: @escaping (BrightnessMessage?, FlutterError?) -> Void) {
let message = BrightnessMessage()
message.brightnessValue = 50 // long-running operation to obtain the brightness
completion(message, nil)
}
func setDeviceBrightness(_ input: BrightnessMessage, error: AutoreleasingUnsafeMutablePointer<FlutterError?>) {
// ...
}
}
// calling the Flutter-Api (the Flutter-Api implementation must be instantiated on the Dart side)
flutterApi.getDefaultDeviceBrightness() { (brightness, error) -> () in
// ...
}
flutterApi.getPreferredDeviceBrightness() { (brightness, error) -> () in
// ...
}
let message = BrightnessMessage()
message.brightnessValue = 50
flutterApi.onDeviceBrightnessChanged(message) { (error) -> () in
// ...
}
Conventions
File structure
- Pigeon configuration, Api definitions, and data-model definitions related to a single feature should reside in the same file
- All messages files should have a
messages
suffix (e.g.,brightness_messages.dart
) - All messages files should be located in the
pigeons/
folder, in the root of the project (e.g.,/pigeons/brightness_messages.dart
) - Generated files should have a
pigeon
suffix (e.g.,brightness_pigeon.dart
)
Messages file
- Order of file contents: Pigeon configuration, Api definitions (host-Api and Flutter-Api), data-model definitions
Pigeon invocation
- Set the Pigeon configuration in the messages file instead of in the invocation
- Save the invocation command in the project readme
Platform code
- Prefer to use Kotlin on Android
- Prefer to use Swift on iOS