はじめに
Flutter プラグインは、Dart とネイティブ(Android/iOS)をつなぐ橋です。
しかし、単に「ネイティブコードを呼べればOK」ではなく、
設計の良し悪しがそのまま保守性・拡張性・テスト容易性に直結します。
この記事では、Flutter公式ガイドラインと実務経験をもとに、
「理想的なプラグイン設計原則と規範」を整理します。
1. プラグインとは何か?
Flutter プラグイン = Dart API + 各プラットフォーム実装 + チャネル通信層
Flutter Plugin
├── Dart層(共通API)
├── Android層(Kotlin/Java)
└── iOS層(Swift/Objective-C)
Dart 層とネイティブ層は MethodChannel などを通して通信します。
2. 設計原則(Design Principles)
① 単一責任(Single Responsibility)
プラグインは 1つの目的 に特化する。
例:battery_info、location_service、file_pickerなど。
複数機能を混ぜると、更新や依存解決が難しくなります。
② インターフェース分離(Interface Segregation)
Dart 層で 抽象インターフェースを定義 し、
各プラットフォームはその契約に従って実装します。
abstract class BatteryInfoPlatform {
Future<int> getBatteryLevel();
}
このように抽象化しておくと、モックテストやフェデレーション構成が可能になります。
③ 一貫性(Consistency)
- メソッド名、戻り値、例外を全プラットフォームで統一
- Dart 層から見た挙動が同じであることが重要
例:
- Android:
-1を返す → iOS も同様に-1 - エラー →
PlatformExceptionを投げる
④ 可拡張性(Extensibility)
Web、Windows、macOS など新しいプラットフォームが追加されても、
既存コードを壊さずに拡張できる構造を取る。
つまり Federated Plugin構成 を採用する。
(詳細は別記事:Flutter Federated Plugin(連携プラグイン))
⑤ テスト容易性(Testability)
-
PlatformInterface層をモック化してユニットテスト可能にする - Channel 通信を直接テストせず、抽象APIを介して検証する
class MockBatteryInfo extends BatteryInfoPlatform {
@override
Future<int> getBatteryLevel() async => 100;
}
3. プロジェクト構造(推奨)
battery_info/
├── lib/
│ ├── src/
│ │ └── method_channel_battery_info.dart
│ └── battery_info.dart # Dart APIエントリーポイント
│
├── android/
│ └── src/main/kotlin/.../BatteryInfoPlugin.kt
│
├── ios/
│ └── Classes/BatteryInfoPlugin.swift
│
├── example/
│ └── lib/main.dart
│
├── pubspec.yaml
└── README.md
4. チャネル通信の設計規範
| 種類 | 用途 | 説明 |
|---|---|---|
| MethodChannel | 一回のメソッド呼び出し | 最も一般的。Dart→NativeのRPC通信に利用。 |
| EventChannel | イベントストリーム | 電池残量、センサーなど定期更新データ用。 |
| BasicMessageChannel | カスタムデータ転送 | JSONや文字列を送受信。双方向通信に強い。 |
メソッドチャネルの命名規則
const MethodChannel _channel = MethodChannel('com.example.battery_info');
- パッケージ形式:
com.<organization>.<plugin_name> - メソッド名は lowerCamelCase:
getBatteryLevel - エラーコードは定数化して管理:
result.error("ERR_PERMISSION_DENIED", "Permission required", null);
5. iOS / Android 実装のポイント
Android側(Kotlin)
class BatteryInfoPlugin : FlutterPlugin, MethodCallHandler {
private lateinit var channel: MethodChannel
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(binding.binaryMessenger, "com.example.battery_info")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel(binding.applicationContext)
result.success(batteryLevel)
} else {
result.notImplemented()
}
}
}
iOS側(Swift)
public class BatteryInfoPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "com.example.battery_info", binaryMessenger: registrar.messenger())
let instance = BatteryInfoPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if call.method == "getBatteryLevel" {
result(Int(UIDevice.current.batteryLevel * 100))
} else {
result(FlutterMethodNotImplemented)
}
}
}
6. エラー処理と例外設計
| レイヤ | 対応方法 | 例 |
|---|---|---|
| Dart層 |
PlatformException を使用 |
throw PlatformException(code: 'ERR_XXX') |
| ネイティブ層 | result.error(code, message, details) |
Swift/Kotlinで統一コード管理 |
| 共通ルール |
ERR_ プレフィックス + 意味的コード |
ERR_UNAVAILABLE, ERR_TIMEOUT
|
7. テスト戦略
-
flutter test:Dartロジックの単体テスト -
integration_test:ネイティブ呼び出し統合テスト - Mockを活用した疑似Platform層テスト
test('returns battery level', () async {
BatteryInfoPlatform.instance = MockBatteryInfo();
expect(await BatteryInfo().getBatteryLevel(), 100);
});
8. 命名・バージョン・リリース規範
| 項目 | ルール |
|---|---|
| パッケージ名 | 小文字スネークケース(例:system_battery_info) |
| クラス名 | PascalCase(例:SystemBatteryInfo) |
| バージョン管理 | Semantic Versioning 準拠 |
| CHANGELOG.md | 変更履歴を必ず更新 |
| LICENSE | MIT / Apache 2.0 推奨 |
| README.md | 対応プラットフォーム・使用例・権限情報を明記 |
9. 図で見るプラグイン通信構造
10. まとめ
| 目的 | 設計の要点 |
|---|---|
| 保守性 | 責務を分離し、各層を疎結合に設計する |
| 🔌 統一性 | API命名・戻り値・エラーコードを統一 |
| 拡張性 | Federated Plugin 構成で多プラットフォーム対応 |
| テスト性 | Platform Interface でMock可能に |
| 品質 | ドキュメント+CHANGELOG+Semantic Versioning |
参考リンク