😋

DartからAndroid / iOS等のメソッドを呼び出したい(Flutter MethodChannel)

2022/04/16に公開

1. Flutter MethodChannelとは

Dartからプラットフォーム (Android/iOS等) のメソッドを呼び出したり、プラットフォームからDartのメソッドを呼び出したりするためのAPIです。
※APIとは、「Application Programming Interface」の略称です。ざっくり言うと、その機能を外部から使うために用意されている用意されている窓口だそうです。
https://wa3.i-3-i.info/word12428.html

2. FlutterChannelの仕組み

下記の記事の項目2がとても参考になります。
https://qiita.com/kurun_pan/items/db6c8fa94bbfb5c0c8d7

3. 基本フロー

下記の記事の項目3がとても参考になります。
https://qiita.com/kurun_pan/items/db6c8fa94bbfb5c0c8d7

4. 簡単にFlutter MethodChannelを使ってみよう

今回は、右下の青いボタンを押すと、「名前 says バッテリーの残量%」を表示する機能を実装したいと思います。
※Simulatorで動作させたため、-1%になっているかと思います。。。

5. やってみよう(dart側)

では、実際にアプリを作ってみます。
main.dartにソースコードを追記していきましょう👍

5.1. import文を書こう

import 'package:flutter/services.dart';

5.2. メソッドチャンネルの作成

まずは、メソッドチャンネルの作成を行います。
MethodChannel(メソッドチャンネル名)でメソッドチャンネルをのインスタンスを作成できます。
ついでに、バッテリーの残量に関する変数も定義しておきましょう。

// メソッドチャンネルを作成
static const batteryChannel = MethodChannel('mukku.com/battery');
// バッテリーの残量
String batteryLevel = 'Waiting...';

5.3. 見た目の部分を作ってみよう

画面の中央に、バッテリーの残量を表示します。
そのためには、floatingActionButtonを押す必要があり、そうすることでgetBatteryLevelメソッドが呼び出されます。


  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        // バッテリーの残量を表示
        child: Text(batteryLevel),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          // メソッドチャンネルを使って、バッテリーの残量をbatteryLevelに代入する
          await getBatteryLevel();
        },
        tooltip: 'Get Battery Level',
        child: const Icon(Icons.battery_full),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

5.4. ロジックの部分を作ってみよう

次に、バッテリーの残量を取得するメソッドを書いてみましょう。
batteryChannelのinvokeMehodでiOS側のメソッドを呼び出すことができます。
第一引数にはメソッド名を書きます。
第二引数には引数のデータを書きます。
取得したデータを表示したいので、setState()でbatteryLevelを更新します。

Future getBatteryLevel() async {
    // iOS側に受けわたす引数
    final arguments = {'name': 'Naoki Honda'};
    // バッテリーの残量を取得
    final String newBateryLevel =
        await batteryChannel.invokeMethod('getBatteryLevel', arguments);
    // 画面を再描画する
    setState(() {
      batteryLevel = '$newBateryLevel%';
    });
  }

5.5. 全体像

https://github.com/John-Thailand/method_channel_example
main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // メソッドチャンネルを作成
  static const batteryChannel = MethodChannel('mukku.com/battery');
  // バッテリーの残量
  String batteryLevel = 'Waiting...';

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        // バッテリーの残量を表示
        child: Text(batteryLevel),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          // メソッドチャンネルを使って、バッテリーの残量をbatteryLevelに代入する
          await getBatteryLevel();
        },
        tooltip: 'Get Battery Level',
        child: const Icon(Icons.battery_full),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  Future getBatteryLevel() async {
    // iOSのメソッドに受けわたす引数
    final arguments = {'name': 'Naoki Honda'};
    // バッテリーの残量を取得
    final String newBateryLevel =
        await batteryChannel.invokeMethod('getBatteryLevel', arguments);
    // 画面を再描画する
    setState(() {
      batteryLevel = '$newBateryLevel%';
    });
  }
}

6. やってみよう(iOS側)

では、iOS側を触ってみます。
Xcodeを開きましょう。
RunnerのAppDelegateファイルに処理を追記していきます。

6.1. バッテリーの残量を取得してみよう

下記のコードの「// ここから 〜 // ここまで」が追記したコードになります。
FlutterMethodChannelを準備します。
setMethodCallHandlerメソッドで実際の処理を書きます。
メソッド名が「"getBatteryLevel"」の場合、バッテリーの残量を含めた文字列が返されます。

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
      // ここから
      let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
      // メソッドチャンネル名
      let METHOD_CHANNEL_NAME = "mukku.com/battery"
      // FlutterMethodChannel
      let batteryChannel = FlutterMethodChannel(name: METHOD_CHANNEL_NAME, binaryMessenger: controller as! FlutterBinaryMessenger)
      // setMethodCallHandlerでコールバックを登録
      batteryChannel.setMethodCallHandler({
          (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
          switch call.method {
          case "getBatteryLevel":
              guard let args = call.arguments as? [String: String] else {return}
              let name = args["name"]!
              
              result("\(name) says \(self.receiveBatteryLevel())")
          default:
              result(FlutterMethodNotImplemented)
          }
      })
      // ここまで
      
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
    
    private func receiveBatteryLevel() -> Int {
        let device = UIDevice.current
        device.isBatteryMonitoringEnabled = true
        
        if (device.batteryState == UIDevice.BatteryState.unknown) {
            return -1
        } else {
            return Int(device.batteryLevel * 100)
        }
    }
}

7. まとめ

ネイティブ側の処理を動作させたいのであれば、Flutter MehodChannelを使う必要があることがわかりました。
次回は、Android側でも同じようなことができるかを試したいと思います。

Discussion