【Flutter/Dart】MethodChannelでネイティブコード(Swift/Kotlin)を使用する方法

【Flutter/Dart】MethodChannelでネイティブコード(Swift/Kotlin)を使用する方法

この記事からわかること

  • Flutter/Dartネイティブコード使用する方法
  • MethodChannel使い方

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

MethodChannel:ネイティブコードをDartから呼び出す方法

公式リファレンス:MethodChannel

Flutterで開発しているプロジェクトの中でネイティブコードを呼び出す方法として「MethodChannel」が用意されています。対応しているのは以下のプラットフォームの固有言語になります。

FlutterではDartとネイティブコードは直接やりとりができるわけではなくこの「MethodChannel」が橋渡し役になります。やり取りを行うために特定のチャネルを介してデータを送受信します。

+----------------------+          +-----------------------+
|      Flutter(Dart)   |          |   Native(iOS/Android) |
|                      |          |                       |
|       invokeMethod() | =======> | MethodCallHandler()   |
|                      |          |                       |
|        result()      | <======= | return                |
+----------------------+          +-----------------------+

実装方法

Dart側ではinvokeMethodメソッドを使用して「MethodChannel」を経由して処理を呼び出します。ネイティブ側ではMethodCallHandlerを使用してDart側からの呼び出しに対して処理を実行し結果を返します。ざっくりと実装の流れは以下の通りになります。

  1. Dart側:MethodChannelをインスタンス化
  2. Dart側:invokeMethodをメソッドラベルを指定して呼び出す
  3. ネイティブ側:MethodChannelをインスタンス化
  4. ネイティブ側:setMethodCallHandlerで処理を呼び出し結果を返す

今回はサンプルとしてネイティブコードを使用してバッテリー情報を取得してDart側のUIで表示する実装をしていきたいと思います。全体のコードを最後に記載しておきます。

1.Dart側:MethodChannelをインスタンス化

まずはDart側での呼び出し処理から記述していきます。MethodChannel引数にチャンネル名を指定してインスタンス化します。このチャンネル名はネイティブ側でも指定するものになります。


class _BatteryPageState extends State<BatteryPage> {
  static const methodChannel = MethodChannel('samples.flutter.dev/battery');
  // 〜〜〜〜〜
}

2.Dart側:invokeMethodをメソッドラベルを指定して呼び出す

MethodChannel型の持つinvokeMethodを使用してネイティブのコードの実行結果を取得します。引数にはメソッドラベルを指定します。このメソッドラベルもネイティブ側でも指定するものになります。またinvokeMethodでは返り値はFuture型になります。非同期処理になるのでawaitを付与する必要とエラーをスローする可能性があるので注意してください。


Future<void> _getBatteryLevel() async {
  try {
    // ネイティブコードの処理を呼び出して実行する
    final int level =
        await methodChannel.invokeMethod<int>('getBatteryLevel') ?? -1;

    setState(() {
      batteryLevel = 'Battery level: $level%';
    });
  } on PlatformException catch (e) {
    setState(() {
      batteryLevel = "Error: ${e.message}";
    });
  }
}

3.ネイティブ側:MethodChannelをインスタンス化(Swift)

まずはiOS側から見ていきます。MethodChannelを使用するにはbinaryMessengerが取得できるところとFlutterEngineに紐づいている箇所でしか使用できないので注意してください。SwiftではFlutterMethodChannelでチャンネルをインスタンス化します。

@main
@objc class AppDelegate: FlutterAppDelegate {
    private let channelName = "samples.flutter.dev/battery"
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        
        GeneratedPluginRegistrant.register(with: self)
        
        // ここから
        let controller = window?.rootViewController as! FlutterViewController
        
        let batteryChannel = FlutterMethodChannel(
            name: channelName,
            binaryMessenger: controller.binaryMessenger
        )
        
        // TODO: setMethodCallHandlerの処理を実装

        // ここまで追加
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}

4.ネイティブ側:setMethodCallHandlerで処理を呼び出し結果を返す(Swift)

Dart側に処理結果を送信するためにsetMethodCallHandlerを使用します。コールバックになっており、その中で呼び出し時に指定されたメソッドラベルが取得できます。そして結果をresult()で返してあげればOKです。意図的にFlutterErrorを返すことも可能です。

batteryChannel.setMethodCallHandler { (call, result) in
    if call.method == "getBatteryLevel" {
        let level = self.getBatteryLevel()
        if true {
            result(level)
        } else {
            result(FlutterError(code: "UNAVAILABLE", message: "Battery IOS info unavailable", details: nil))
        }
    } else {
        result(FlutterMethodNotImplemented)
    }
}

バッテリー取得処理は以下のように実装しておきます。詳細はこちらの記事を参考にしてください。

private func getBatteryLevel() -> Int {
    UIDevice.current.isBatteryMonitoringEnabled = true
    let batteryLevel = UIDevice.current.batteryLevel
    return Int(batteryLevel * 100)
}

3.ネイティブ側:MethodChannelをインスタンス化(Kotlin)

続いてAndroid側の実装です。こちらはMethodChannelでインスタンス化することができます。

class MainActivity : FlutterActivity() {
    private val CHANNEL = "samples.flutter.dev/battery"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        MethodChannel(
            flutterEngine.dartExecutor.binaryMessenger,
            CHANNEL
        )
    }
}

4.ネイティブ側:setMethodCallHandlerで処理を呼び出し結果を返す(Kotlin)

Swiftと同じような形式でsetMethodCallHandlerが用意されているので結果を返してあげればOKです。

MethodChannel(
    flutterEngine.dartExecutor.binaryMessenger,
    CHANNEL
).setMethodCallHandler { call, result ->

    when (call.method) {
        "getBatteryLevel" -> {
            val level = getBatteryLevel()
            if (level != -1) {
                result.success(level)
            } else {
                result.error("UNAVAILABLE", "Battery level not available.", null)
            }
        }
        else -> result.notImplemented()
    }
}

バッテリー取得処理は以下のように実装しておきます。

private fun getBatteryLevel(): Int {
    val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
    return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

Search Box

Sponsor

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index