0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Flutter】 Flutter プラグイン設計原則

Posted at

はじめに

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_infolocation_servicefile_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

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?