FlutterからネイティブAPIを呼びたい
MethodChannelを使って、FlutterからKotlin(Android)とSwift(iOS)のネイティブコード呼び出しを試してみました。
Writing custom platform-specific codeに記載のサンプルコードを動かしてみたので、実際の流れをメモしておきます。
Java/Objective-C版はこちら
FlutterからJava/Objective-Cのネイティブコードを呼んでみた - Qiita
サンプルソースはこちら
https://github.com/unsolublesugar/flutter-sample-custom-platform-specific-code
サンプルプロジェクトの作成
まずはサンプルプロジェクトを作成します。サンプルアプリでは、バッテリー残量を取得するネイティブコードを実装します。
$ flutter create batterylevel
新規プロジェクト作成時にKotlinとSwiftとがサポートされていなかった場合は、以下フラグ付きコマンドで作成しなおしてください。
$ flutter create -i swift -a kotlin batterylevel
※ドキュメントによると、デフォルトでJava、Objective-Cがサポートされているとのことですが、v1.9.1ではオプションなしでKotlinとSwiftが使えました。
-i, --ios-language [objc, swift (default)]
-a, --android-language [java, kotlin (default)]
Flutter側の呼び出しコード
サンプル画面のWidgetとバッテリー残量取得メソッドの実装を加えます。以下、_MyHomePageState
のみ抜粋したmain.dart
ファイルです。
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
...
class _MyHomePageState extends State<MyHomePage> {
static const platform = const MethodChannel('samples.flutter.dev/battery');
String _batteryLevel = 'Unknown battery level.';
Future<void> _getBatteryLevel() async {
// Get battery level.
String batteryLevel;
try {
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
setState(() {
_batteryLevel = batteryLevel;
});
}
@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
child: Text('Get Battery Level'),
onPressed: _getBatteryLevel,
),
Text(_batteryLevel),
],
),
),
);
}
}
MethodChannel
で呼び出すバッテリー残量取得メソッドgetBatteryLevel
は、KotlinとSwift側でそれぞれ定義していきます。
なお、ひとつのアプリで使用されるチャンネル名は、一意である必要があります。チャンネル名の前にはドメインプレフィックスを付けます。
MethodChannel('samples.flutter.dev/battery');
Kotlin側のネイティブコード(Android)
それではKotlin(Android)のコードを書いていきましょう。
必要なimportの追加
MainActivity.kt
にimportを追加。
import android.os.Bundle
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import io.flutter.app.FlutterActivity
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugin.common.MethodChannel
getBatteryLevelを実装
MainActivity
のonCreate
メソッド内でMethodChannel
を定義し、setMethodCallHandler
を呼び出します。
class MainActivity: FlutterActivity() {
private val CHANNEL = "samples.flutter.dev/battery"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
}
}
}
ここで定義したチャンネル名samples.flutter.dev/battery
を、Flutter側の呼び出しで使用します。
Android端末のバッテリーレベルを取得するコードを追加します。
private fun getBatteryLevel(): Int {
val batteryLevel: Int
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
}
return batteryLevel
}
getBatteryLevel()
をMethodChannel
で呼び出すコードを追加。
private val CHANNEL = "samples.flutter.dev/battery"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
} else {
result.notImplemented()
}
}
}
これでAndroid用Kotlinネイティブコードの呼び出し準備は完了です。
エミュレーター上で実行
上記コードをエミュレーター上でbuild runすると「Get Battery Level」ボタンと「Unknown battery level.」テキストが表示されます。
ボタンタップで、端末のバッテリー残量を取得。テキスト反映されます。
サクッと呼べましたね。
Swift側のネイティブコード(iOS)
続いてSwift(iOS)のコードを書いていきます。
XcodeでAppDelegate.swiftを開く
Xcodeを起動し、対象プロジェクト内のiOSフォルダを開きます。Runner
フォルダ配下にあるAppDelegate.swift
を開きます。
getBatteryLevelを実装
Androidと同じくMethodChannel
の実装を定義します。
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let batteryChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery",
binaryMessenger: controller.binaryMessenger)
batteryChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
// Note: this method is invoked on the UI thread.
// Handle battery messages.
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
バッテリーレベルの取得メソッドreceiveBatteryLevel
を追加。
private func receiveBatteryLevel(result: FlutterResult) {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
if device.batteryState == UIDevice.BatteryState.unknown {
result(FlutterError(code: "UNAVAILABLE",
message: "Battery info unavailable",
details: nil))
} else {
result(Int(device.batteryLevel * 100))
}
}
setMethodCallHandler
でreceiveBatteryLevel
を呼び出します。
batteryChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
// Note: this method is invoked on the UI thread.
guard call.method == "getBatteryLevel" else {
result(FlutterMethodNotImplemented)
return
}
self?.receiveBatteryLevel(result: result)
})
iOS Simulatorを起動すると、Android同様にボタンとテキストが表示されます。
ボタンをタップするとMethodChannel経由でreceiveBatteryLevel()
が呼び出されテキストが更新されます。
残念ながらiOS Simulator上ではバッテリーAPIがサポートされていないようですが「Battery info unavailable」というSwift側で書いたコードが動いていることがわかりますね。
ネイテイブからFlutterへのコールバック通知について
ネイテイブからFlutter側へ状態変化等のコールバック通知を行うには、EventChennelという仕組みを利用するそうです。
EventChannel class - services library - Dart API
こちらはまだ試していないので、別途触ってみたいと思います。