diff --git a/packages/pigeon/README.md b/packages/pigeon/README.md index cc375f5e2fa..a5865c156b1 100644 --- a/packages/pigeon/README.md +++ b/packages/pigeon/README.md @@ -3,11 +3,341 @@ Pigeon is a code generator tool to make communication between Flutter and the host platform type-safe, easier, and faster. -Pigeon removes the necessity to manage strings across multiple platforms and languages. -It also improves efficiency over common method channel patterns. Most importantly though, -it removes the need to write custom platform channel code, since pigeon generates it for you. +Pigeon works by reading a special file or files, which are placed outside of the +`/lib` directory which hosts all of your application code. You define special +data classes and endpoints, which Pigeon will then consume and use to generate +matching Dart and native code at the paths you specify. -For usage examples, see the [Example README](./example/README.md). +Internally, the generated code uses `MethodChannel`s to communicate between Flutter's +UI thread and the Platform thread where your host app is initially launched. The value +in Pigeon comes from automatically keeping this unpleasant boilerplate in sync and +efficiently marshalling data between languages. + +The generated code can either flow from Dart to native code, or native code back +to Dart. Generated code on the receiving end uses interfaces or abstract classes, +allowing you to provide implementations in concrete classes. + +Pigeon works in both complete Flutter apps and hybrid apps using the add-to-app paradigm. + +## Quickstart + +### Installation + +Begin by adding Pigeon to your Dart project's `pubspec.yaml` file: + +```sh +$ flutter pub add pigeon --dev +$ flutter pub get +``` + +### Setup + +To specify what code Pigeon should generate, create your interface definition file. +This guide will place all such definitions at `/pigeons/messages.dart`, but you +are free to choose any file name you like, inside a dedicated folder or not. + +Begin by instantiating a `PigeonOptions` object, wrapped in the `@ConfigurePigeon` +decorator. Later, you will pass named parameters to your `PigeonOptions` object +to specify your desired behavior. + +```dart +// pigeons/messages.dart + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions() +); +``` + +### Define your messages + +The next step to use Pigeon is to define the data structures Dart will exchange +with native code. You do this by writing plain Dart enums or classes in your +definition file. These messages should only contain simple constructors and direct +attributes. Methods, factory constructors, constructor bodies, and computed +properties are all not allowed. + +```dart +// pigeons/messages.dart + +enum Code { a, b } + +class MessageData { + MessageData({required this.code, required this.data}); + String? name; + String? description; + Code code; + Map data; +} +``` + +No extra steps are necessary to register these classes - their inclusion +in your definitions file ensures Pigeon will generate matching Dart and native +implementations. + +### Define which methods to expose + +The point of Pigeon and the `MethodChannel`s it utilizes is to call native functions +living on the Platform thread from Dart, or to call Dart functions living on the UI +thread from native code. Either way, you must declare methods in your definitions +file which tell Pigeon what function signatures its generated code must support. + +#### Call native code from Dart + +To expose a native function to be called from Dart, write an abstract class in +your Pigeon file and mark it with `@HostApi()`. + +```dart +// pigeons/messages.dart + +@HostApi() +abstract class ExampleHostApi { + String getHostLanguage(); + int add(int a, int b); + + @async + void sendMessage(MessageData message); +} +``` + +> Note: For more information on the `@async` decorator, see the [section on +> asynchronous](#Synchronous-and-Asynchronous-methods) methods below. + +Later, the Pigeon generator will produce a matching interface in native code and +you will register a concrete implementation. This concrete version of the class +will be where you either perform the necessary native operations or call out to +other native libraries. + +#### Call Dart code from native + +To expose a Dart function to your app's native code, write an abstract class in +your Pigeon file and mark it with `@FlutterApi()`. + +```dart +// pigeons/messages.dart + +@FlutterApi() +abstract class MessageFlutterApi { + String flutterMethod(String? aString); +} +``` + +Later, you will register a concrete implementation of this Dart abstract class, but +for now this is enough for you to run the generator. The concrete class you supply +will be the bridge to the rest of your application's business logic. + +### Configure your output + +It is time to pass values to the `PigeonOptions` object to configure your desired +behavior. To begin, specify a `dartOut` value where your Dart code should live and, +optionally, a `DartOptions` instance. + +```dart +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + dartOptions: DartOptions(), // Optional + ), +) +``` +Next, add sections for each native platform your app should support. + +> Note: The paths to your native projects can vary depending on whether your +> app is entirely Flutter, or whether you are adding Flutter into an existing +> native app. These code paths will assume your app is entirely Flutter, but +> add-to-app users should see the [`Add to app usage`](Add-to-app-usage) +> section for specific guidance. + + +#### Add iOS and/or macOS support + +To instruct Pigeon to generate Swift code for your app on iOS, provide a path +for the `swiftOut` parameter and, optionally, a `SwiftOptions` instance. + +```dart +@ConfigurePigeon( + PigeonOptions( + ... + swiftOut: 'ios/Runner/Messages.g.swift', + swiftOptions: SwiftOptions(), // Optional + ), +) +``` + +To instruct Pigeon to generate Objective-C code for your app on iOS, provide paths +for the `objcHeaderOut` and `objcSourceOut` parameters and, optionally, an +`ObjcOptions` instance. + +```dart +@ConfigurePigeon( + PigeonOptions( + ... + objcHeaderOut: 'ios/Runner/Messages.g.h', + objcSourceOut: 'ios/Runner/Messages.g.m', + // Set this to a unique prefix for your plugin or application, per Objective-C naming conventions. + objcOptions: ObjcOptions(prefix: 'PGN'), + ), +) +``` + +> Note: Pigeon generates code on a per-language basis, not a per-platform basis. +> This is important for Pigeon to support all the platforms Flutter will build +> to in the future, but it does introduce a wrinkle if you want the same definitions +> generated for two different platforms; e.g., Swift code on iOS and macOS. To +> achieve this, you can either symlink your iOS files into your macOS directory, +> or you can use a separate Pigeon file (e.g., `/pigeons/macos_messages.dart`) +> which specifies macOS paths (e.g., `macos/Runner/Messages.g.swift`). + +#### Add Android support + +To instruct Pigeon to generate Kotlin code for your app on Android, provide a path +for the `kotlinOut` parameter and, optionally, a `KotlinOptions` instance. + +```dart +@ConfigurePigeon( + PigeonOptions( + ... + kotlinOut: 'android/app/src/main/kotlin/dev/flutter/my_app_name/Messages.g.kt', + kotlinOptions: KotlinOptions(), // Optional + ), +) +``` + +To instruct Pigeon to generate Java code for your app on Android, provide a path +for the `javaOut` parameter and, optionally, a `JavaOptions` instance. + +```dart +@ConfigurePigeon( + PigeonOptions( + ... + javaOut: 'android/app/src/main/java/io/flutter/plugins/Messages.java', + javaOptions: JavaOptions(), // Optional + ), +) +``` + +#### Add Windows support + +To instruct Pigeon to generate C++ code for your app on Windows, provide paths +for the `cppHeaderOut` and `cppSourceOut` parameters and, optionally, a +`CppOptions` instance. + +```dart +@ConfigurePigeon( + PigeonOptions( + ... + cppHeaderOut: 'windows/runner/messages.g.h', + cppSourceOut: 'windows/runner/messages.g.cpp', + cppOptions: CppOptions(namespace: 'pigeon_example'), + ), +) +``` + +#### Add Linux support + +To instruct Pigeon to generate GObject code for your app on Linux, provide paths +for the `gobjectHeaderOut` and `gobjectSourceOut` parameters and, optionally, a +`GObjectOptions` instance. + +```dart +@ConfigurePigeon( + PigeonOptions( + ... + gobjectHeaderOut: 'linux/messages.g.h', + gobjectSourceOut: 'linux/messages.g.cc', + gobjectOptions: GObjectOptions(), + ), +) +``` + +#### A note on Web support + +Pigeon does not support the Web because Flutter apps compiled to the Web can +already directly call any JavaScript code without switching threads, as is +currently required in Flutter on mobile or desktop. Pigeon generates code which +performs two roles: + +1. Using Flutter's `MethodChannel`s concept to jump from the UI thread to the +Platform thread (or in the other direction), and +2. Marshalling data between Dart and the native language. + +Flutter Web apps intrinsically do not encounter the first problem and only +encounter a form of the second problem if you need to interface with a +library's `d.ts` interface. In this case, you may need to author custom Dart +classes which match those TypeScript definitions. Check pub.dev for existing solutions. + +### Run the builder + +Once your Pigeon files define any data classes and functions you wish to invoke, +you can run the builder: + +```sh +$ dart run pigeon --input pigeons/messages.dart +``` + +### Use the generated code + +You should now see matching output files at the locations you specified in your +`PigeonOptions` instance, or in your command line arguments. The two primary +scenarios to explore are calling native code from Dart and calling Dart code from +native. + +#### Calling native code from Dart + +The sample code in this Quickstart defines an example native API named `ExampleHostApi`. +Pigeon will generate a complete Dart implementation and the equivalent of an abstract +class in each native language (for example, a `protocol` in Swift and an `interface` +in Kotlin). + +See the [language-specific guides](example/README.md#HostApi-Example) for help instantiating the native classes +generated by Pigeon. + +In Dart, typical use within a `StatefulWidget` might look like this: + +```dart +late final ExampleHostApi; + +@override +void initState() { + _hostApi = ExampleHostApi(); + super.initState(); +} + +Future getHostLanguage() async + => _hostApi.getHostLanguage(); + +Future add(int a, int b) async + => _hostApi.add(a, b); + +Future sendMessage(MessageData message) async + => _hostApi.sendMessage(message); +``` + +#### Calling Dart code from native + +The sample code in this Quickstart defined an example Dart API named `MessageFlutterApi`. +Define a concrete implementation of this abstract class with your real business logic: + +```dart +class _MessageFlutterApi implements MessageFlutterApi { + @override + String flutterMethod(String? aString) => aString ?? ''; +} +``` + +Next, register your implementation with the `MessageChannel` harness that Pigeon +generated. You must complete this call to `setUp` before invoking the +`flutterMethod` method from your native code. + +```dart +MessageFlutterApi.setUp(_MessageFlutterApi()); +``` + +Pigeon will have generated a native implementation of `MessageFlutterApi` in your +designated languages and files, and you should now be ready to instantiate that +class and invoke its methods. See the language-specific sections below for help +instantiating the native classes generated by Pigeon. ## Features @@ -82,16 +412,6 @@ to the api to allow for multiple instances to be created and operate in parallel ## Usage -1) Add pigeon as a `dev_dependency`. -1) Make a ".dart" file outside of your "lib" directory for defining the - communication interface. -1) Run pigeon on your ".dart" file to generate the required Dart and - host-language code: `flutter pub get` then `dart run pigeon` - with suitable arguments. [Example](./example/README.md#Invocation). -1) Add the generated Dart code to `./lib` for compilation. -1) Implement the host-language code and add it to your build (see below). -1) Call the generated Dart methods. - ### Rules for defining your communication interface [Example](./example/README.md#HostApi_Example) @@ -151,6 +471,66 @@ but reversed. For more information look at the annotation `@FlutterApi()` which denotes APIs that live in Flutter but are invoked from the host platform. [Example](./example/README.md#FlutterApi_Example). +### Add to app usage + +Apps which are primarily Flutter from the beginning have slightly different structures +from apps which are native from the beginning and then later introduce Flutter. +These differences do not materially change how to use Pigeon, but they do of +course influence the paths you supply to various commands. + +In Flutter-first apps, your structure might look like this: + +``` +android/ + ... +ios/ + Runner/ + Messages.g.swift +lib/ + main.dart + src/ + pigeon_messages.g.dart +pigeon_messages.dart +pubspec.yaml +``` + +This would suggest `PigeonOptions` values like so: + +```dart +PigeonOptions( + dartOut: 'lib/src/pigeon_messages.g.dart', + kotlinOut: 'android/app/src/main/kotlin/dev/flutter/app_name/Messages.g.kt' + swiftOut: 'ios/Runner/Messages.g.swift', +) +``` + +However, in native-first apps, your structure might look like this: + +``` +android_app_name/ + ... +ios_app_name/ + Messages.g.swift +flutter_module/ + .ios/ + .android/ + lib/ + src/ + pigeon_messages.dart + pigeon_messages.dart + pubspec.yaml +``` + +Such an add-to-app setup would suggest `PigeonValues` like so: + +```dart +PigeonOptions( + dartOut: 'lib/src/pigeon_messages.g.dart', + kotlinOut: '../android_app_name/app/src/main/kotlin/dev/flutter/app_name/Messages.g.kt' + swiftOut: '../ios_app_name/Messages.g.swift', +) +``` + ## Feedback File an issue in [flutter/flutter](https://github.com/flutter/flutter) with diff --git a/packages/pigeon/example/README.md b/packages/pigeon/example/README.md index 7e7fbb6d5af..5c7de09f86d 100644 --- a/packages/pigeon/example/README.md +++ b/packages/pigeon/example/README.md @@ -1,48 +1,14 @@ # Pigeon Examples +For a quick start guide to Pigeon, see the [QuickStart guide](../README.md#Quickstart). +For language-specific guides to instantiate code generated by Pigeon, continue with +this guide. + The examples here will cover basic usage. For a more thorough set of examples, check the [core_tests pigeon file](../pigeons/core_tests.dart) and [platform test folder](../platform_tests/) ([shared_test_plugin_code](../platform_tests/shared_test_plugin_code/) and [alternate_language_test_plugin](../platform_tests/alternate_language_test_plugin/) especially). -## Invocation - -Begin by configuring pigeon at the top of the `.dart` input file. -In actual use, you would include only the languages -needed for your project. - - -```dart -@ConfigurePigeon(PigeonOptions( - dartOut: 'lib/src/messages.g.dart', - dartOptions: DartOptions(), - cppOptions: CppOptions(namespace: 'pigeon_example'), - cppHeaderOut: 'windows/runner/messages.g.h', - cppSourceOut: 'windows/runner/messages.g.cpp', - gobjectHeaderOut: 'linux/messages.g.h', - gobjectSourceOut: 'linux/messages.g.cc', - gobjectOptions: GObjectOptions(), - kotlinOut: - 'android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt', - kotlinOptions: KotlinOptions(), - javaOut: 'android/app/src/main/java/io/flutter/plugins/Messages.java', - javaOptions: JavaOptions(), - swiftOut: 'ios/Runner/Messages.g.swift', - swiftOptions: SwiftOptions(), - objcHeaderOut: 'macos/Runner/messages.g.h', - objcSourceOut: 'macos/Runner/messages.g.m', - // Set this to a unique prefix for your plugin or application, per Objective-C naming conventions. - objcOptions: ObjcOptions(prefix: 'PGN'), - copyrightHeader: 'pigeons/copyright.txt', - dartPackageName: 'pigeon_example_package', -)) -``` -Then make a simple call to run pigeon on the Dart file containing your definitions. - -```sh -dart run pigeon --input path/to/input.dart -``` - ## HostApi Example This example gives an overview of how to use Pigeon to call into the