Mobile Development 14 min read

Using Pigeon to Manage Flutter Plugin Interfaces Across Platforms

Pigeon lets Flutter developers define a single Dart API that automatically generates matching, type‑safe interface code for Android and iOS, eliminating manual method‑channel boilerplate, preventing naming mismatches, and keeping native and Dart implementations synchronized across platforms.

Tencent Music Tech Team
Tencent Music Tech Team
Tencent Music Tech Team
Using Pigeon to Manage Flutter Plugin Interfaces Across Platforms

In cross‑platform development, plugins often suffer from naming and parameter inconsistencies when multiple native sides are involved. This article explains how Flutter’s Pigeon tool can generate unified interface code, reducing manual method‑channel handling and keeping Dart and native implementations in sync.

Why Pigeon is needed – Maintaining a separate protocol file for Dart, Android, and iOS can easily become out‑of‑date, leading to mismatched APIs. Pigeon generates the boiler‑plate for both sides from a single Dart definition, ensuring type safety and consistent naming.

Pigeon’s role – By providing a Dart entry point, Pigeon creates template code for Android (Java/Kotlin) and iOS (Objective‑C/Swift). Developers only need to implement the generated abstract methods; the method‑channel plumbing is handled automatically.

Getting started

1. Create a Flutter plugin package (skip if you already have one):

flutter create --org com.example --template plugin flutterPigeonDemo

2. Add Pigeon as a dev dependency in pubspec.yaml :

dev_dependencies:
  flutter_test:
    sdk: flutter
  pigeon:
    version: 0.1.7

3. Create a pigeons directory and add a Dart file (e.g., pigeons/pigeonDemoMessage.dart ) that defines request/response classes and an @HostApi interface:

import 'package:pigeon/pigeon.dart';

class DemoReply {
  String result;
}

class DemoRequest {
  String methodName;
}

@HostApi()
abstract class PigeonDemoApi {
  DemoReply getMessage(DemoRequest params);
}

void configurePigeon(PigeonOptions opts) {
  opts.dartOut = './lib/PigeonDemoMessage.dart';
  opts.objcHeaderOut = 'ios/Classes/PigeonDemoMessage.h';
  opts.objcSourceOut = 'ios/Classes/PigeonDemoMessage.m';
  opts.objcOptions.prefix = 'FLT';
  opts.javaOut = 'android/src/main/kotlin/com/example/flutter_pigeon_demo/PigeonDemoMessage.java';
  opts.javaOptions.package = 'package com.example.flutter_pigeon_demo';
}

4. Run the generator:

flutter pub run pigeon --input pigeons/pigeonDemoMessage.dart

The command produces Dart, Java/Kotlin, and Objective‑C files placed in the paths defined above.

Android integration

Import the generated Java file ( PigeonDemoMessage.java ) and let your plugin class implement PigeonDemoApi . Register the API in onAttachedToEngine :

// ... other imports
import com.example.flutter_pigeon_demo.PigeonDemoMessage.*;

public class FlutterPigeonDemoPlugin implements FlutterPlugin, MethodCallHandler, PigeonDemoApi {
    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
        MethodChannel channel = new MethodChannel(binding.getBinaryMessenger(), "flutter_pigeon_demo");
        channel.setMethodCallHandler(this);
        // Register the generated API
        PigeonDemoApi.setup(binding.getBinaryMessenger(), this);
    }

    @Override
    public DemoReply getMessage(DemoRequest arg) {
        DemoReply reply = new DemoReply();
        reply.result = "pigeon demo result";
        return reply;
    }
}

iOS integration

Import the generated header ( PigeonDemoMessage.h ) and register the API in registerWithRegistrar :

#import "FlutterPigeonDemoPlugin.h"
#import "PigeonDemoMessage.h"

@implementation FlutterPigeonDemoPlugin
+ (void)registerWithRegistrar:(NSObject
*)registrar {
    FlutterPigeonDemoPlugin* instance = [[FlutterPigeonDemoPlugin alloc] init];
    // Register the generated API
    FLTPigeonDemoApiSetup(registrar.messenger, instance);
}

- (FLTDemoReply*)getMessage:(FLTDemoRequest*)input error:(FlutterError**)error {
    FLTDemoReply* reply = [[FLTDemoReply alloc] init];
    reply.result = @"pigeon demo result";
    return reply;
}
@end

Dart side usage

After generation, the Dart file ( PigeonDemoMessage.dart ) contains the data classes and the API stub. Call the API like any normal Dart method:

import 'dart:async';
import 'package:flutter/services.dart';
import 'PigeonDemoMessage.dart';

class FlutterPigeonDemo {
  static const MethodChannel _channel = MethodChannel('flutter_pigeon_demo');

  static Future
get platformVersion async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }

  static Future
testPigeon() async {
    DemoRequest request = DemoRequest()..methodName = 'requestMessage';
    PigeonDemoApi api = PigeonDemoApi();
    DemoReply reply = await api.getMessage(request);
    return reply;
  }
}

Effect of using Pigeon

Before Pigeon, plugin code often mixes many MethodChannel calls with string‑based method names, leading to spaghetti code. After integration, each method has a strongly‑typed Dart signature, and the native side only implements the generated abstract methods. This improves readability, type safety, and reduces boilerplate.

Conclusion

Pigeon enables a single source of truth for Flutter plugin interfaces, automatically generating synchronized code for Dart, Android, and iOS. By maintaining only the Dart protocol file, developers can avoid mismatched APIs, reduce repetitive code, and focus on business logic.

dartFlutterNativepluginCodeGenerationCross‑platformPigeon
Tencent Music Tech Team
Written by

Tencent Music Tech Team

Public account of Tencent Music's development team, focusing on technology sharing and communication.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.