LoginSignup
7
2

More than 1 year has passed since last update.

flutterで端末の気圧センサーを用いたpluginを自作して公開してみた

Posted at

下準備

まずこれを呼び出します。

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を用いて実装しました。

FlutterBarometerPlugin.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側の実装

SwiftFlutterBarometerPlugin.swift
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側の実装

lib/flutter_barometer.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()でリアルタイムで受け取るようにしています。これでセンサー値をリアルタイムで受け取れます。

example/lib/main.dart
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

一度公開してしまうと削除できないので気をつけて下さい。

成果物

参考文献

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2