bridge_native
A Flutter plugin that provides type-safe communication between Flutter and native platforms (Android/iOS) using Pigeon. This plugin contains autogenerated Pigeon code and Flutter-side utilities for seamless Flutter-to-native integration.
What is bridge_native?
bridge_native is a foundation plugin that provides:
- Autogenerated Pigeon code for type-safe communication
- Request-Response models (Req/Res classes)
- Flutter utilities (
sendToNative()function andNativewidget) - Platform interfaces that your main plugin implements
This plugin is typically used as a dependency by other plugins that need Flutter-to-native communication.
Features
- Type-Safe Models: Autogenerated
ReqandResclasses using Pigeon - ReqApi Interface: Type-safe API for native implementations
- Flutter Utilities:
sendToNative(): Send requests from Flutter to nativeNativewidget: Wrapper to receive native events
- Cross-Platform: Works with both Android (Java) and iOS (Objective-C)
- Zero Manual Serialization: Pigeon handles all serialization automatically
Installation
Add this to your plugin's pubspec.yaml:
dependencies:
bridge_native: ^1.0.0
Then run:
flutter pub get
Architecture
┌─────────────────────────────────────┐
│ Your Flutter Plugin │
│ │
│ Uses: sendToNative() │
│ Native widget │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ bridge_native │
│ │
│ Provides: │
│ • Req/Res models │
│ • ReqApi interface │
│ • Pigeon autogenerated code │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Your Native Implementation │
│ │
│ Implements: ReqApi │
│ Sets up: EventChannel (optional) │
└─────────────────────────────────────┘
Usage
1. Flutter Side - Use Provided Utilities
Send Request to Native
import 'package:bridge_native/bridge/native.dart';
// Send request to native and receive response
Future<void> getDeviceInfo() async {
final response = await sendToNative(
key: 'getDeviceInfo',
data: {'includeModel': true},
);
print('Response: ${response.data}');
}
Listen to Native Events
import 'package:bridge_native/bridge/native.dart';
import 'package:flutter/material.dart';
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Native(
child: YourContent(),
doNativeDataBack: (data) {
// Handle data from native
print('Native event: $data');
if (data is Map) {
final key = data['key']?.toString() ?? '';
final payload = data['data'];
// Process event...
}
},
);
}
}
2. Native Side - Implement ReqApi
The autogenerated code provides interfaces/protocols that you must implement in your native plugin.
Android (Java)
package com.example.yourplugin;
import androidx.annotation.NonNull;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.EventChannel;
import vn.son.bridge.Pigeon;
import java.util.HashMap;
import java.util.Map;
public class YourPlugin implements FlutterPlugin {
private EventChannel eventChannel;
private EventChannel.EventSink eventSink;
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
// 1. Setup Pigeon API
Pigeon.ReqApi.setup(binding.getBinaryMessenger(), new AndroidAPI());
// 2. Setup EventChannel for sending events to Flutter
eventChannel = new EventChannel(binding.getBinaryMessenger(), "bridgeStream");
eventChannel.setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
eventSink = events;
}
@Override
public void onCancel(Object arguments) {
eventSink = null;
}
});
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
eventChannel.setStreamHandler(null);
}
// Implement ReqApi interface
class AndroidAPI implements Pigeon.ReqApi {
@NonNull
@Override
public Pigeon.Res request(@NonNull Pigeon.Req request) {
// Handle request from Flutter
String key = request.getKey();
Map<Object, Object> data = request.getData();
// Process based on key
switch (key) {
case "getDeviceInfo":
return handleDeviceInfo(data);
case "openCamera":
return handleCamera(data);
default:
return createResponse("unknown", null);
}
}
private Pigeon.Res handleDeviceInfo(Map<Object, Object> data) {
Map<String, Object> result = new HashMap<>();
result.put("model", android.os.Build.MODEL);
result.put("version", android.os.Build.VERSION.RELEASE);
return createResponse("deviceInfo", result);
}
private Pigeon.Res createResponse(String key, Map<String, Object> data) {
Pigeon.Res response = new Pigeon.Res();
response.setKey(key);
response.setData((Map) data);
return response;
}
}
// Send event to Flutter
public void sendEventToFlutter(String key, Map<String, Object> data) {
if (eventSink != null) {
Map<String, Object> event = new HashMap<>();
event.put("key", key);
event.put("data", data);
eventSink.success(event);
}
}
}
iOS (Swift)
import Flutter
import bridge_native
public class YourPlugin: NSObject, FlutterPlugin, ReqApi {
private var eventChannel: FlutterEventChannel?
private var eventSink: FlutterEventSink?
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = YourPlugin()
// 1. Setup EventChannel for sending events to Flutter
let channel = FlutterEventChannel(
name: "bridgeStream",
binaryMessenger: registrar.messenger()
)
channel.setStreamHandler(instance)
instance.eventChannel = channel
// 2. Setup Pigeon API
ReqApiSetup(registrar.messenger(), instance)
}
// Implement ReqApi protocol
public func requestRequest(
_ request: Req,
error: AutoreleasingUnsafeMutablePointer<FlutterError?>
) -> Res? {
let res = Res()
switch request.key {
case "getDeviceInfo":
res.key = "deviceInfo"
res.data = [
"model": UIDevice.current.model,
"version": UIDevice.current.systemVersion
]
case "openCamera":
// Handle camera
res.key = "cameraOpened"
res.data = ["success": true]
default:
res.key = "unknown"
res.data = nil
}
return res
}
// Send event to Flutter
func sendEventToFlutter(key: String, data: [String: Any]) {
let event: [String: Any] = [
"key": key,
"data": data
]
eventSink?(event)
}
}
// MARK: - FlutterStreamHandler
extension YourPlugin: FlutterStreamHandler {
public func onListen(
withArguments arguments: Any?,
eventSink events: @escaping FlutterEventSink
) -> FlutterError? {
self.eventSink = events
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
self.eventSink = nil
return nil
}
}
API Reference
Flutter API
sendToNative({key, data})
Sends a request to native code and awaits response.
Parameters:
key(dynamic): Request identifierdata(dynamic): Request payload (typically Map)
Returns:
Future<Res>: Response from native
Example:
final res = await sendToNative(
key: 'action',
data: {'param': 'value'},
);
Native Widget
A widget wrapper that receives events from native code via EventChannel.
Properties:
child(Widget, required): Your widget contentdoNativeDataBack(Function(dynamic), required): Callback for native events
Example:
Native(
child: MyContent(),
doNativeDataBack: (data) {
// Handle event
},
)
Native API
Android - Pigeon.ReqApi Interface
public interface ReqApi {
@NonNull
Res request(@NonNull Req request);
}
Setup:
Pigeon.ReqApi.setup(binaryMessenger, new YourImplementation());
iOS - ReqApi Protocol
@protocol ReqApi
- (nullable Res *)requestRequest:(Req *)request
error:(FlutterError **)error;
@end
Setup:
ReqApiSetup(binaryMessenger, yourImplementation);
Data Models
Req - Request Model
| Field | Type | Description |
|---|---|---|
| key | String? | Request identifier |
| data | Map? | Request payload |
Res - Response Model
| Field | Type | Description |
|---|---|---|
| key | String? | Response identifier |
| data | Map? | Response payload |
Complete Example
See the example directory for a complete working example of using bridge_native.
Key implementation points:
- Flutter: Uses
sendToNative()andNativewidget - Android: Implements
Pigeon.ReqApiinEkycPlugin.kt - iOS: Implements
ReqApiprotocol inEkycPlugin.swift - Both platforms setup EventChannel named
"bridgeStream"
Important Notes
EventChannel Setup
bridge_native does NOT setup EventChannel for you. You must:
- Create EventChannel in your plugin's native code
- Use the name
"bridgeStream"(required by Native widget) - Send events as Map with
keyanddatafields
Pigeon Versions
This plugin uses Pigeon 10.1.4 for code generation. The generated code is compatible with:
- Android: Java/Kotlin
- iOS: Objective-C/Swift
Communication Pattern
Request → Response (Synchronous-like):
- Use
sendToNative()from Flutter - Native implements
ReqApiand returnsRes
Event Streaming (Asynchronous):
- Native sends to EventChannel
- Flutter listens via
Nativewidget
Troubleshooting
"No implementation found for method request"
Cause: ReqApi not properly setup in native code.
Solution:
- Android: Call
Pigeon.ReqApi.setup(messenger, yourImpl)inonAttachedToEngine - iOS: Call
ReqApiSetup(messenger, yourImpl)inregister(with:)
Events not received in Flutter
Cause: EventChannel not setup or wrong channel name.
Solution:
- Verify channel name is exactly
"bridgeStream" - Ensure
Nativewidget is mounted in widget tree - Check that native is calling
eventSink?.success(data)
Type casting errors
Solution: Always type-check dynamic data:
if (data is Map) {
final key = data['key']?.toString() ?? '';
// Safe to use
}
How bridge_native Works
What It Provides
-
Autogenerated Pigeon Code:
ReqandResclasses (Android + iOS)ReqApiinterface/protocol- Codec for serialization
-
Flutter Utilities:
sendToNative()function (wraps Pigeon API)Nativewidget (wraps EventChannel listener)- Convenience functions for common patterns
What You Must Implement
-
In Your Native Plugin:
- Implement
ReqApiinterface/protocol - Setup EventChannel if you need native → Flutter events
- Handle requests in
request()method
- Implement
-
In Your Flutter Plugin:
- Use
sendToNative()to send requests - Use
Nativewidget to receive events - Process responses and events
- Use
Development
Regenerating Pigeon Code
If you need to modify the API:
- Edit
pigeons/br_api.dart - Run Pigeon code generation:
flutter pub run pigeon --input pigeons/br_api.dart
Testing
Test your implementation:
test('sendToNative returns response', () async {
final res = await sendToNative(key: 'test', data: {});
expect(res, isNotNull);
expect(res.key, isNotNull);
});
Best Practices
-
Define Key Constants:
class BridgeKeys { static const getDeviceInfo = 'getDeviceInfo'; static const openCamera = 'openCamera'; } -
Type-Safe Data Handling:
if (data is Map<String, dynamic>) { final value = data['key'] as String?; } -
Error Handling:
try { final res = await sendToNative(key: 'action', data: {}); } catch (e) { print('Bridge error: $e'); } -
Document Your Protocol:
- List all supported keys
- Define data structures
- Document response formats
Additional Resources
License
This project is licensed under the MIT License. See the LICENSE file for details.
Contributing
Contributions are welcome! Please submit issues or pull requests.
Repository: https://github.com/sonuos1501/bridge_native Maintainer: SON (Lucas)