<?code-excerpt path-base="app"?>
# Golubets Examples

The examples here will cover basic usage. For a more thorough set of examples,
check the [core_tests golubets 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 golubets at the top of the `.dart` input file.
In actual use, you would include only the languages
needed for your project.

<?code-excerpt "pigeons/messages.dart (config)"?>
```dart
@ConfigureGolubets(
  GolubetsOptions(
    dartOut: 'lib/src/messages.g.dart',
    dartOptions: DartOptions(),
    cppOptions: CppOptions(namespace: 'golub_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: 'golubets_example_package',
  ),
)
```
Then make a simple call to run golubets on the Dart file containing your definitions.

```sh
dart run golubets --input path/to/input.dart
```

## Calling into iOS/Android from Flutter

This example gives an overview of how to use Golubets to call into the

### Dart input

This is the Golubets file that describes the interface that will be used to call
from Flutter to the host-platform.

<?code-excerpt "pigeons/messages.dart (host-definitions)"?>
```dart
enum Code { one, two }

class MessageData {
  MessageData({
    this.code = Code.one,
    this.data = const <String, String>{
      'hello': 'world',
      'lorem': 'ipsum',
      'golubets': 'rocks',
    },
    this.name = 'Golub',
    this.description = 'Example description',
  });

  String? name;
  String? description;
  Code code;
  Map<String, String> data;
}

@HostApi()
abstract class ExampleHostApi {
  String getHostLanguage();

  // These annotations create more idiomatic naming of methods in Objc and Swift.
  @ObjCSelector('addNumber:toNumber:')
  @SwiftFunction('add(_:to:)')
  int add(int a, int b);

  @async
  bool sendMessage(MessageData message);
  // This annotation generates an await-style asynchronous method,
  // unlike the callback-based approach used in sendMessage.
  // In Swift, this method does not throw exceptions (`isSwiftThrows: false`).
  // Will return true if the message was sent from background thread.
  @TaskQueue(type: TaskQueueType.serialBackgroundThread)
  @Async(type: AsyncType.await(isSwiftThrows: false))
  bool sendMessageModernAsync(MessageData message);

  // The same as sendMessageModernAsync, but throws an exception.
  @TaskQueue(type: TaskQueueType.serialBackgroundThread)
  @Async(type: AsyncType.await(isSwiftThrows: true))
  bool sendMessageModernAsyncThrows(MessageData message);
}
```

### Dart

This is the code that will use the generated Dart code to make calls from Flutter to 
the host platform.

<?code-excerpt "lib/main.dart (main-dart)"?>
```dart
final ExampleHostApi _api = ExampleHostApi();

/// Calls host method `add` with provided arguments.
Future<int> add(int a, int b) async {
  try {
    return await _api.add(a, b);
  } catch (e) {
    // handle error.
    return 0;
  }
}

/// Sends message through host api using `MessageData` class
/// and api `sendMessage` method.
Future<bool> sendMessage(String messageText) {
  final MessageData message = MessageData(
    data: <String, String>{'header': 'this is a header'},
    description: 'uri text',
  );
  try {
    return _api.sendMessage(message);
  } catch (e) {
    // handle error.
    return Future<bool>(() => true);
  }
}

Future<bool> sendMessageModernAsync(String messageText) {
  final MessageData message = MessageData(
    code: Code.two,
    data: <String, String>{'header': 'this is a header'},
    description: 'uri text',
  );

  return _api.sendMessageModernAsync(message);
}

Future<bool> sendMessageModernAsyncAndThrow(String messageText) {
  final MessageData message = MessageData(
    code: Code.two,
    data: <String, String>{'header': 'this is a header'},
    description: 'uri text',
  );

  return _api.sendMessageModernAsyncThrows(message);
}
```

### Swift

This is the code that will use the generated Swift code to receive calls from Flutter.
Unlike other languages, when throwing an error, use `GolubetsError` instead of `FlutterError`, as `FlutterError` does not conform to `Swift.Error`.
<?code-excerpt "ios/Runner/AppDelegate.swift (swift-class)"?>
```swift
private class GolubetsApiImplementation: ExampleHostApi {
  func getHostLanguage() throws -> String {
    return "Swift"
  }

  func add(_ a: Int64, to b: Int64) throws -> Int64 {
    if a < 0 || b < 0 {
      throw GolubetsError(code: "code", message: "message", details: "details")
    }
    return a + b
  }

  func sendMessage(message: MessageData, completion: @escaping (Result<Bool, Error>) -> Void) {
    if message.code == Code.one {
      completion(.failure(GolubetsError(code: "code", message: "message", details: "details")))
      return
    }
    completion(.success(true))
  }

  /// Unlike implementations on other platforms, this function does not throw any exceptions
  /// because the `@Async(type: AsyncType.await(isSwiftThrows: false))` annotation was specified.
  func sendMessageModernAsync(message: MessageData) async -> Bool {
    return !Thread.isMainThread
  }

  func sendMessageModernAsyncThrows(message: MessageData) async throws -> Bool {
    if message.code == .one {
      return !Thread.isMainThread
    }

    throw GolubetsError(code: "code", message: "message", details: "details")
  }
}

```

### Kotlin
<?code-excerpt "android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt (kotlin-class)"?>
```kotlin
private class PigeonApiImplementation : ExampleHostApi {
  override fun getHostLanguage(): String {
    return "Kotlin"
  }

  override fun add(a: Long, b: Long): Long {
    if (a < 0L || b < 0L) {
      throw FlutterError("code", "message", "details")
    }
    return a + b
  }

  override fun sendMessage(message: MessageData, callback: (Result<Boolean>) -> Unit) {
    if (message.code == Code.ONE) {
      callback(Result.failure(FlutterError("code", "message", "details")))
      return
    }
    callback(Result.success(true))
  }

  override suspend fun sendMessageModernAsync(message: MessageData): Boolean {
    if (message.code == Code.ONE) {
      throw FlutterError("code", "message", "details")
    }

    return Thread.currentThread() != Looper.getMainLooper().getThread()
  }

  override suspend fun sendMessageModernAsyncThrows(message: MessageData): Boolean {
    if (message.code == Code.ONE) {
      return Thread.currentThread() != Looper.getMainLooper().getThread()
    }

    throw FlutterError("code", "message", "details")
  }
}
```

### C++
<?code-excerpt "windows/runner/flutter_window.cpp (cpp-class)"?>
```c++
class GolubApiImplementation : public ExampleHostApi {
 public:
  GolubApiImplementation() {}
  virtual ~GolubApiImplementation() {}

  ErrorOr<std::string> GetHostLanguage() override { return "C++"; }
  ErrorOr<int64_t> Add(int64_t a, int64_t b) {
    if (a < 0 || b < 0) {
      return FlutterError("code", "message", "details");
    }
    return a + b;
  }
  void SendMessage(const MessageData& message,
                   std::function<void(ErrorOr<bool> reply)> result) {
    if (message.code() == Code::kOne) {
      result(FlutterError("code", "message", "details"));
      return;
    }
    result(true);
  }
```

### GObject
<?code-excerpt "linux/my_application.cc (vtable)"?>
```c++
static GolubetsExamplePackageExampleHostApiGetHostLanguageResponse*
handle_get_host_language(gpointer user_data) {
  return golubets_example_package_example_host_api_get_host_language_response_new(
      "C++");
}

static GolubetsExamplePackageExampleHostApiAddResponse* handle_add(
    int64_t a, int64_t b, gpointer user_data) {
  if (a < 0 || b < 0) {
    g_autoptr(FlValue) details = fl_value_new_string("details");
    return golubets_example_package_example_host_api_add_response_new_error(
        "code", "message", details);
  }

  return golubets_example_package_example_host_api_add_response_new(a + b);
}

static void handle_send_message(
    GolubetsExamplePackageMessageData* message,
    GolubetsExamplePackageExampleHostApiResponseHandle* response_handle,
    gpointer user_data) {
  GolubetsExamplePackageCode code =
      golubets_example_package_message_data_get_code(message);
  if (code == GOLUBETS_EXAMPLE_PACKAGE_CODE_ONE) {
    g_autoptr(FlValue) details = fl_value_new_string("details");
    golubets_example_package_example_host_api_respond_error_send_message(
        response_handle, "code", "message", details);
    return;
  }

  golubets_example_package_example_host_api_respond_send_message(
      response_handle, TRUE);
}
// ···
static GolubetsExamplePackageExampleHostApiVTable example_host_api_vtable = {
    .get_host_language = handle_get_host_language,
    .add = handle_add,
    .send_message = handle_send_message,
    // ···
};
```

## FlutterApi Example

This example gives an overview of how to use Golub to call into the Flutter
app from the host platform.

### Dart input

<?code-excerpt "pigeons/messages.dart (flutter-definitions)"?>
```dart
@FlutterApi()
abstract class MessageFlutterApi {
  String flutterMethod(String? aString);
}

```

### Dart

This is the code that will use the generated Dart code to handle calls made to 
Flutter from the host platform.

<?code-excerpt "lib/main.dart (main-dart-flutter)"?>
```dart
class _ExampleFlutterApi implements MessageFlutterApi {
  @override
  String flutterMethod(String? aString) {
    return aString ?? '';
  }
}
// ···
  MessageFlutterApi.setUp(_ExampleFlutterApi());
```

### Swift

<?code-excerpt "ios/Runner/AppDelegate.swift (swift-class-flutter)"?>
```swift
private class GolubetsFlutterApi {
  var flutterAPI: MessageFlutterApi

  init(binaryMessenger: FlutterBinaryMessenger) {
    flutterAPI = MessageFlutterApi(binaryMessenger: binaryMessenger)
  }

  func callFlutterMethod(
    aString aStringArg: String?, completion: @escaping (Result<String, GolubetsError>) -> Void
  ) {
    flutterAPI.flutterMethod(aString: aStringArg) {
      completion($0)
    }
  }
}

```

### Kotlin

<?code-excerpt "android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt (kotlin-class-flutter)"?>
```kotlin
private class PigeonFlutterApi(binding: FlutterPlugin.FlutterPluginBinding) {
  var flutterApi: MessageFlutterApi? = null

  init {
    flutterApi = MessageFlutterApi(binding.binaryMessenger)
  }

  fun callFlutterMethod(aString: String, callback: (Result<String>) -> Unit) {
    flutterApi!!.flutterMethod(aString) { echo -> callback(echo) }
  }
}
```

### C++

<?code-excerpt "windows/runner/flutter_window.cpp (cpp-method-flutter)"?>
```c++
class GolubetsFlutterApi {
 public:
  GolubetsFlutterApi(flutter::BinaryMessenger* messenger)
      : flutterApi_(std::make_unique<MessageFlutterApi>(messenger)) {}

  void CallFlutterMethod(
      const std::string& a_string,
      std::function<void(ErrorOr<std::string> reply)> result) {
    flutterApi_->FlutterMethod(
        &a_string, [result](const std::string& echo) { result(echo); },
        [result](const FlutterError& error) { result(error); });
  }

 private:
  std::unique_ptr<MessageFlutterApi> flutterApi_;
};
```

### GObject

<?code-excerpt "linux/my_application.cc (flutter-method-callback)"?>
```c++
static void flutter_method_cb(GObject* object, GAsyncResult* result,
                              gpointer user_data) {
  g_autoptr(GError) error = nullptr;
  g_autoptr(
      GolubetsExamplePackageMessageFlutterApiFlutterMethodResponse) response =
      golubets_example_package_message_flutter_api_flutter_method_finish(
          GOLUBETS_EXAMPLE_PACKAGE_MESSAGE_FLUTTER_API(object), result, &error);
  if (response == nullptr) {
    g_warning("Failed to call Flutter method: %s", error->message);
    return;
  }

  g_printerr(
      "Got result from Flutter method: %s\n",
      golubets_example_package_message_flutter_api_flutter_method_response_get_return_value(
          response));
}
```

<?code-excerpt "linux/my_application.cc (flutter-method)"?>
```c++
self->flutter_api =
    golubets_example_package_message_flutter_api_new(messenger, nullptr);
golubets_example_package_message_flutter_api_flutter_method(
    self->flutter_api, "hello", nullptr, flutter_method_cb, self);
```

## Event Channel Example

This example gives a basic overview of how to use Pigeon to set up an event channel.

### Dart input

<?code-excerpt "pigeons/event_channel_messages.dart (event-definitions)"?>
```dart
@EventChannelApi()
abstract class EventChannelMethods {
  PlatformEvent streamEvents();
}

```

### Dart

The generated Dart code will include a method that returns a `Stream` when invoked. 

<?code-excerpt "lib/main.dart (main-dart-event)"?>
```dart
Stream<String> getEventStream() async* {
  final Stream<PlatformEvent> events = streamEvents();
  await for (final PlatformEvent event in events) {
    switch (event) {
      case IntEvent():
        final int intData = event.data;
        yield '$intData, ';
      case StringEvent():
        final String stringData = event.data;
        yield '$stringData, ';
      case EmptyEvent():
        yield '';
    }
  }
}
```

### Swift

Define the stream handler class that will handle the events.

<?code-excerpt "ios/Runner/AppDelegate.swift (swift-class-event)"?>
```swift
class EventListener: StreamEventsStreamHandler {
  var eventSink: GolubEventSink<PlatformEvent>?

  override func onListen(withArguments arguments: Any?, sink: GolubEventSink<PlatformEvent>) {
    eventSink = sink
  }

  func onIntEvent(event: Int64) {
    if let eventSink = eventSink {
      eventSink.success(.intEvent(data: event))
    }
  }

  func onStringEvent(event: String) {
    if let eventSink = eventSink {
      eventSink.success(.stringEvent(data: event))
    }
  }

  func onEmptyEvent() {
    if let eventSink = eventSink {
      eventSink.success(.emptyEvent)
    }
  }

  func onEventsDone() {
    eventSink?.endOfStream()
    eventSink = nil
  }
}

```

Register the handler with the generated method.

<?code-excerpt "ios/Runner/AppDelegate.swift (swift-init-event)"?>
```swift
let eventListener = EventListener()
StreamEventsStreamHandler.register(
  with: controller.binaryMessenger, streamHandler: eventListener)
```

### Kotlin

Define the stream handler class that will handle the events.

<?code-excerpt "android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt (kotlin-class-event)"?>
```kotlin
class EventListener : StreamEventsStreamHandler() {
  private var eventSink: GolubEventSink<PlatformEvent>? = null

  override fun onListen(p0: Any?, sink: GolubEventSink<PlatformEvent>) {
    eventSink = sink
  }

  fun onIntEvent(event: Long) {
    eventSink?.success(IntEvent(data = event))
  }

  fun onStringEvent(event: String) {
    eventSink?.success(StringEvent(data = event))
  }

  fun onEmptyEvent() {
    eventSink?.success(EmptyEvent())
  }

  fun onEventsDone() {
    eventSink?.endOfStream()
    eventSink = null
  }
}
```


Register the handler with the generated method.

<?code-excerpt "android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt (kotlin-init-event)"?>
```kotlin
val eventListener = EventListener()
StreamEventsStreamHandler.register(flutterEngine.dartExecutor.binaryMessenger, eventListener)
```

## Swift / Kotlin Plugin Example

A downloadable example of using Golub to create a Flutter Plugin with Swift and
Kotlin can be found at
[gaaclarke/flutter_plugin_example](https://github.com/gaaclarke/pigeon_plugin_example).

## Swift / Kotlin Add-to-app Example

A full example of using Golub for add-to-app with Swift on iOS can be found at
[samples/add_to_app/books](https://github.com/flutter/samples/tree/master/add_to_app/books).

## Video player plugin

A full real-world example can also be found in the
[video_player plugin](https://github.com/flutter/packages/tree/main/packages/video_player).
