Platform specific code
Last modified on Tue 09 Nov 2021

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

Messages file

Pigeon invocation

Platform code