Closed12
Flutter の MethodChannel を使ってみる
はじめに
このスクラップでは Flutter の Method Channel クラスを使って下記をやってみる
- MethodChannel を使って iOS SDK の機能を呼び出す
- MethodChannel を使って iOS サードパーティライブラリを呼び出す
- MethodChannel を使って Android SDK の機能を呼び出す
- MethodChannel を使って Android サードパーティライブラリ を呼び出す
Method Channel については今のところよくわからないがプラットフォームに固有の機能を呼び出す方法らしい
前回
前回は下記のFlutterの情報取得パッケージを使ってみた
- package_info_plus
- device_info_plus
- permission_handler
ドキュメント
参考になりそうなドキュメント
FlutterMethodChannel と MethodChannel
FlutterMethodChannel は iOS 、MethodChannel は Android のようだ
On the platform side, MethodChannel on Android (MethodChannelAndroid) and FlutterMethodChannel on iOS (MethodChanneliOS) enable receiving method calls and sending back a result.
アーキテクチャ
公式ドキュメントの図がわかりやすいので Mermaid で作図してみた
ドキュメントの導入部を少し読んだ
- UI と Platform は非同期メッセージパッシングでやり取りする
- タイプセーフなメッセージを送受信するには Pigeon を使う
- defaultTargetPlatform を使うことでプラットフォーム固有の Dart コードを書けるらしい
- メッセージはメインスレッドで送受信する必要がある
- 通常は UI → Platform にリクエストするが逆も可能
- メッセージは StandardMessageCode を使ってエンコード/デコードされる
- 普通に MethodChannel を使う分には
flutter create
でプロジェクトを作成して OK - パッケージを作成する場合には Developping plugin packages のページを参考にする
- Client と Android Platform は MethodChannel を使い、iOS Platform は FlutterMethodChannel を使う
Step 2: Create the Flutter platform client からはチュートリアル内容なので、これに従って手順を進めてみようと思う
Android コードを呼び出す
準備コマンド
flutter create batterylevel
cd batterylevel
コード
batterylevel/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const platform = MethodChannel('samples.flutter.dev/battery');
String _batteryLevel = 'Unknown battery level.';
Future<void> _getBatteryLevel() async {
String batteryLevel;
try {
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level as $result % .';
} on PlatformException catch (e) {
batteryLevel = "failed to get battery level: '${e.message}'.";
}
setState(
() {
_batteryLevel = batteryLevel;
},
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
ElevatedButton(
onPressed: _getBatteryLevel,
child: const Text('Get Battery Level'),
),
Text(_batteryLevel),
],
),
),
);
}
}
batterylevel/android/app/src/main/kotlin/com/example/batterylevel/MainActivity.kt
package com.example.batterylevel
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 androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
private val CHANNEL = "samples.flutter.dev/battery"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine);
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, 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()
}
}
}
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
}
}
実行コマンド
Android 実機で実行する
flutter run
実行結果
iOS コードを呼び出す
準備コマンド
flutter create batterylevel
cd batterylevel
コード
batterylevel/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const platform = MethodChannel('samples.flutter.dev/battery');
String _batteryLevel = 'Unknown battery level.';
Future<void> _getBatteryLevel() async {
String batteryLevel;
try {
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level as $result % .';
} on PlatformException catch (e) {
batteryLevel = "failed to get battery level: '${e.message}'.";
}
setState(
() {
_batteryLevel = batteryLevel;
},
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
ElevatedButton(
onPressed: _getBatteryLevel,
child: const Text('Get Battery Level'),
),
Text(_batteryLevel),
],
),
),
);
}
}
batterylevel/ios/Runner/AppDelegate.swift
import Flutter
import UIKit
@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({
[weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
guard call.method == "getBatteryLevel" else {
result(FlutterMethodNotImplemented)
return
}
self?.receiveBatteryLevel(result: result)
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func receiveBatteryLevel(result: FlutterResult) {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
if device.batteryState == UIDevice.BatteryState.unknown {
result(
FlutterError(
code: "UNAVAILABLE",
message: "Battery level not available",
details: nil
))
} else {
result(Int(device.batteryLevel * 100))
}
}
}
実行コマンド
iOS 実機で実行する
flutter run
実行結果
build.gradle について
依存関係を追加したい場合は buildscript
の dependencies
ではなくルートの dependencies
に追加すると良さそう。
Android のサードパーティライブラリを呼び出す
準備コマンド
flutter create hello_gson
cd hello_gson
コード
hello_gson/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const platform = MethodChannel('loremipsum.co.jp/gson');
String _message = '';
Future<void> _getMessage() async {
String message;
try {
message = await platform.invokeMethod('getMessage');
} on PlatformException catch (e) {
message = 'Failed to get a message: ${e.message}';
}
setState(() {
_message = message;
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
ElevatedButton(
onPressed: _getMessage,
child: const Text('Get Message'),
),
Text(_message),
],
),
),
);
}
}
hello_gson/android/app/src/main/kotlin/com/example/hello_gson/MainActivity.kt
package com.example.hello_gson
import androidx.annotation.NonNull
import com.google.gson.Gson
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
private val CHANNEL = "loremipsum.co.jp/gson"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "getMessage") {
val gson = Gson()
val message = gson.toJson("Message")
result.success(message)
} else {
result.notImplemented()
}
}
}
}
android/app/build.gradle
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "com.google.code.gson:gson:2.10"
}
実行コマンド
Android 実機で実行する
flutter run
実行結果
iOS のサードパーティライブラリを呼び出す
準備コマンド
flutter create hello_swifty_json
cd hello_swifty_json
pod init
コード
hello_swifty_json/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const platform = MethodChannel('loremipsum.co.jp/gson');
String _message = '';
Future<void> _getMessage() async {
String message;
try {
message = await platform.invokeMethod('getMessage');
} on PlatformException catch (e) {
message = 'Failed to get a message: ${e.message}';
}
setState(() {
_message = message;
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
ElevatedButton(
onPressed: _getMessage,
child: const Text('Get Message'),
),
Text(_message),
],
),
),
);
}
}
hello_swifty_json/ios/Podfile
# Uncomment the next line to define a global platform for your project
# platform :ios, '11.0'
target 'Runner' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for Runner
pod 'SwiftyJSON', '~> 5.0'
end
hello_swifty_json/ios/Runner/AppDelegate.swift
import Flutter
import SwiftyJSON
import UIKit
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(
name: "loremipsum.co.jp/gson",
binaryMessenger: controller.binaryMessenger
)
channel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
guard call.method == "getMessage" else {
result(FlutterMethodNotImplemented)
return
}
result(JSON(["message": "I'm a json"]).rawString())
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
実行コマンド
iOS 実機で実行する
pod install
flutter run
実行結果
おわりに
以上で一旦クローズ、次は UnitTest を使ってみる
このスクラップは2023/01/10にクローズされました