下準備
まずこれを呼び出します。
flutter create --template=plugin --platforms=android,ios -a java flutter_barometer
テンプレート作成時に追加されている、 example
ディレクトリで
flutter build apk
flutter build ios --no-codesign
を実行し、事前にアプリをビルドします。
作成
Android
Android Studioで [プラグイン名]/example/android/build.gradle
を選択してプロジェクトを開きます。
サイドバーにある[プラグイン名]/java/[パッケージ名]/[プラグイン名]Plugin
というファイルに実装していきます。
iOS
Xcodeで [プラグイン名]/example/ios/Runner.xcworkspace
を選択してプロジェクトを開きます。
サイドバーにある
Pods/Development Pods/hello/../../example/ios/.symlinks/plugins/hello/ios/Classes/Swift[プラグイン名]Plugin.swift
を選択して実装していきます。
Android側の実装
javaを用いて実装しました。
public class FlutterBarometerPlugin implements FlutterPlugin, MethodCallHandler, SensorEventListener {
private MethodChannel channel;
private EventChannel stream;
private SensorManager sensorManager;
private EventChannel.EventSink sinkOnChanged;
public Sensor pressureSensor;
private Context applicationContext = null;
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
applicationContext = null;
sensorManager.unregisterListener(this);
channel.setMethodCallHandler(null);
}
// センサー値が変わった時
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
double pressure = sensorEvent.values[0];
if (sinkOnChanged != null) {
sinkOnChanged.success(pressure);
}
}
// センサー値の精度が変わった時
@Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
// センサーの初期化処理. 公式document参照.
private boolean initializePressureSensor() {
sensorManager = (SensorManager) applicationContext.getSystemService(Context.SENSOR_SERVICE);
if (sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE) != null) {
pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
sensorManager.registerListener(this, pressureSensor, SensorManager.SENSOR_DELAY_NORMAL);
return true;
} else {
pressureSensor = null;
return false;
}
}
private EventChannel.StreamHandler handler() {
return new EventChannel.StreamHandler() {
@Override
public void onListen(Object listener, EventChannel.EventSink events) {
if (initializePressureSensor()) {
sinkOnChanged = events;
}
}
@Override
public void onCancel(Object args) {
sinkOnChanged = null;
}
};
}
// 最初に呼ばれる初期化処理
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
applicationContext = flutterPluginBinding.getApplicationContext();
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "flutter_barometer");
channel.setMethodCallHandler(this);
stream = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "pressureStream");
stream.setStreamHandler(handler());
}
// Flutter側から呼ばれた時発動
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
// getPlatFormVersionが呼ばれた時
if (call.method.equals("getPlatformVersion")) {
// この値が返る
result.success("Android " + android.os.Build.VERSION.RELEASE);
return;
}
result.notImplemented();
}
}
まず最初にonAttachedToEngine()が呼ばれ、その中でMethodChannelとEvanChannelが初期化されます。その後FlutterからMethodChannel.invokeMethod()が呼ばれた時にonMethodCallが呼び出され、result.success()で引数に値を入れる事でFlutter側に値を返すことができます(Future型)。また、sinkOnChanged.success()の引数に値を入れることでFlutter側に値を返すことができます(Stream型)。Stream型でリアルタイムに値を返すことができるので端末のセンサーを用いたアプリを開発したい場合こちらになるかと思います。
iOS側の実装
import Flutter
import UIKit
import CoreMotion
public class SwiftFlutterBarometerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
var altimeter:CMAltimeter?
var pressure:Double = 0.0
var sinkOnChanged: FlutterEventSink?
var listeningOnChanged = false
// ここで初期化していいのかはわかりません.取りあえず動きます.
override init() {
super.init()
altimeter = CMAltimeter()
getCurrentPressure()
}
// センサー値を取得
private func getCurrentPressure() {
if(CMAltimeter.isRelativeAltitudeAvailable()) {
altimeter!.startRelativeAltitudeUpdates(
to: OperationQueue.main,
withHandler:
{data, error in
if error == nil {
let pressure:Double = data!.pressure.doubleValue
self.pressure = pressure * 10
self.onChanged(value: self.pressure);
}
}
)
}
}
//まずこれが呼ばれる
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "flutter_barometer", binaryMessenger: registrar.messenger())
let stream = FlutterEventChannel(name: "pressureStream", binaryMessenger: registrar.messenger())
let instance = SwiftFlutterBarometerPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
stream.setStreamHandler(instance)
}
// EventChannel 初期化
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
self.sinkOnChanged = events
listeningOnChanged = true
return nil
}
// EventChannel 終了処理
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
listeningOnChanged = false
return nil
}
// EventChannel 値を流す
public func onChanged(value: Double) {
if listeningOnChanged {
if let sink = self.sinkOnChanged {
sink(value)
}
}
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
// これが呼ばれた時に
if call.method == "getPlatformVersion" {
// これを返す
result("iOS " + UIDevice.current.systemVersion)
}
if call.method == "getCurrentPressure" {
result(self.pressure)
}
}
}
swiftもAndroid側のjavaと同様、まずregister()が走り、その後MethodChannel.invokeMethodが呼ばれた時はhandle()が呼ばれてFlutter側にFuture型で値を返し、sinkOnChanged()ではStream型で値を返します。
Dart側の実装
import 'dart:async';
import 'package:flutter/services.dart';
class FlutterBarometer {
static const MethodChannel _channel =
const MethodChannel('flutter_barometer');
static const EventChannel _barometerEventChannel =
EventChannel('pressureStream');
static var _onPressureChanged;
static Future<String?> get platformVersion async {
final String? version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
static Future<double> get currentPressue async {
final pressure = await _channel.invokeMethod('getCurrentPressure');
return pressure;
}
static Stream<double> get currentPressueEvent {
if (_onPressureChanged == null) {
_onPressureChanged = _barometerEventChannel.receiveBroadcastStream().map(
(element) => element as double,
);
}
return _onPressureChanged;
}
}
currentPressueEventではEventChanelでsinkされた値をreceiveBroadcastStream()でリアルタイムで受け取るようにしています。これでセンサー値をリアルタイムで受け取れます。
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_barometer/flutter_barometer.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
double _currentPressure = 0.0;
@override
void initState() {
super.initState();
initPlatformState();
FlutterBarometer.currentPressueEvent.listen((event) {
setState(() {
_currentPressure = event;
});
});
}
Future<void> initPlatformState() async {
String platformVersion;
try {
platformVersion =
await FlutterBarometer.platformVersion ?? 'Unknown platform version';
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('flutter_barometer'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Running on: $_platformVersion\n',
style: TextStyle(fontSize: 30),
),
Text(
'${(_currentPressure * 1000).round() / 1000} hPa',
style: TextStyle(
fontSize: 70,
),
)
],
),
),
),
);
}
}
こんな感じで使います。
いざ公開
外部へ公開する場合、以下の4点が必要です。
- README.md (そのプラグインの解説を記載したファイル)
- CHANGELOG.md (各バージョンでの変更を記載したファイル)
- LICENSE (そのプラグインのライセンスを記載したファイル)
- パブリックにしているAPIに対してのコメントの記述(今回
lib/flutter_barometer.dart
に何も記述していないのでpub pointが減点されている。)
公開するプラグインが問題ないかを事前に --dry-run をおこなってチェックし、
flutter pub publish --dry-run
以下のコマンドで公開します。
flutter pub publish
一度公開してしまうと削除できないので気をつけて下さい。
成果物
参考文献