Slide 1

Slide 1 text

FlutterPluginの作り方 GDG DevFest Tokyo 2018 @kuwapp_dev

Slide 2

Slide 2 text

自己紹介 ● @kuwapp_dev / 畑祐輔(はたゆうすけ) ● モイ株式会社 ● Androidエンジニア ● ツイキャス

Slide 3

Slide 3 text

このセッションのゴール

Slide 4

Slide 4 text

このセッションのゴール ● このセッションを見た方が、FlutterPluginとはなにか理解す ること ● このセッションを見た方が、FlutterPluginを実装できるように なること

Slide 5

Slide 5 text

Agenda

Slide 6

Slide 6 text

Agenda ● FlutterPluginとは ● MethodChannel ● EventChannel ● MethodCodec ● TIPS ● まとめ

Slide 7

Slide 7 text

Flutter Pluginとは

Slide 8

Slide 8 text

Flutter Pluginとは ● iOS・Androidの実装とそれらを使用するためのDartの実装 を含むパッケージのこと。 ● DartからAndroidやiOSの実装を呼び出すこともできるし、そ の逆もできる。 ● Flutterでプラットフォーム固有の機能を使用したい場合に使 用する。

Slide 9

Slide 9 text

Flutter(Dart)からAndroid・iOSのAPIにアクセスが必要なときに 使用する。デバイス固有の機能を使用したいとき。 例えば ● 位置情報を取得したい ● 位置情報の変更を監視したい ● アラームを設定したい ● 音楽を再生したい ● Bluetooth機能を使いたい どのような場合に使用するのか

Slide 10

Slide 10 text

作り方をなぜ知っておく必要があるのか 1. アプリの要求に適したPluginが存在するとは限らない。 2. 既存のPlugin選定時に知っておきたい。

Slide 11

Slide 11 text

1.アプリの要求に適したPluginが 存在するとは限らない ● 既に提供されているプラグインだけでアプリの機能を実装で きるとは限らない。 ● 自分で実装が必要な場合もある。 ● 実装方法を知っておくことでどの程度工数がかかりそうなど イメージができる。

Slide 12

Slide 12 text

2.既存のPluginの選定時に知っておきたい ● 実際に使えるレベルのものであるのか。 ● Pluginを書いた人がAndroid・iOSそれぞれに精通している とは限らないので品質が低い場合がある。

Slide 13

Slide 13 text

ざっくりとPluginの動作イメージ

Slide 14

Slide 14 text

Android iOS Pluginの動作イメージ Flutter

Slide 15

Slide 15 text

Android iOS Pluginの動作イメージ Flutter Android・iOSのAPIを呼び出す

Slide 16

Slide 16 text

Android iOS Pluginの動作イメージ Flutter 結果を返す

Slide 17

Slide 17 text

Android iOS Pluginの動作イメージ Flutter MethodChannel EventChannel

Slide 18

Slide 18 text

MethodChannelとEventChannel MethodChannelやEventChannelがDartと各プラットフォームの 橋渡しを行っている。 これらを使用してFlutterとAndroid・iOSでやりとりを行う。

Slide 19

Slide 19 text

MethodChannel

Slide 20

Slide 20 text

MethodChannel ● Dartから各プラットフォームのAPIを呼び出し、Dart側へ値を 返却できる。 ● 各プラットフォームからDartのAPI呼び出しができる。

Slide 21

Slide 21 text

MethodChannel Flutter invokeMethod invokeMethod success, error, notImplemented Android iOS

Slide 22

Slide 22 text

MethodChannel Flutter invokeMethod invokeMethod success, error, notImplemented Android iOS

Slide 23

Slide 23 text

MethodChannel Flutter invokeMethod invokeMethod success, error, notImplemented Android iOS

Slide 24

Slide 24 text

MethodChannel Flutter invokeMethod invokeMethod success, error, notImplemented Android iOS

Slide 25

Slide 25 text

Dartから各プラットフォームのAPIを 呼び出し結果を受け取る例

Slide 26

Slide 26 text

Pluginの仕様 端末のモデル名を取得できる ● AndroidならNexus5など ● iOSならiPhoneやiPadなど デバイス名を取得するPluginを作る

Slide 27

Slide 27 text

Pluginのプロジェクトを作る flutter create --org com.kuwapp --template=plugin -i swift -a kotlin device_plugin templateはPluginを指定

Slide 28

Slide 28 text

Pluginのプロジェクトを作る flutter create --org com.kuwapp --template=plugin -i swift -a kotlin device_plugin iOS・Androidの言語指定 デフォルトはObjCとJava

Slide 29

Slide 29 text

Pluginのプロジェクトを作る flutter create --org com.kuwapp --template=plugin -i swift -a kotlin device_plugin Plugin名の指定

Slide 30

Slide 30 text

デバイス名を取得するメソッド名を決める デバイス名を取得する機能を追加するので getDeviceModel とする これは後ほどDart、Android、iOSの実装で使用する。

Slide 31

Slide 31 text

各プラットフォームの実装をDartから呼び出す ● Dartから各プラットフォームの実装を呼び出すために MethodChannelを使用する。 ● MethodChannelがDartと各プラットフォーム間の橋渡しを行 う。 ● 生成されたdevice_plugin.dart を変更を加えていく。

Slide 32

Slide 32 text

device_plugin.dart class DevicePlugin { static const MethodChannel _channel = const MethodChannel('device_plugin'); static Future get deviceModel async { final String deviceModel = await _channel.invokeMethod('getDeviceModel'); return deviceModel; } } MethodChannelの インスタンス生成

Slide 33

Slide 33 text

device_plugin.dart class DevicePlugin { static const MethodChannel _channel = const MethodChannel('device_plugin'); static Future get deviceModel async { final String deviceModel = await _channel.invokeMethod('getDeviceModel'); return deviceModel; } } invokeMethodで各プラットフォー ムの実装が呼び出される

Slide 34

Slide 34 text

device_plugin.dart class DevicePlugin { static const MethodChannel _channel = const MethodChannel('device_plugin'); static Future get deviceModel async { final String deviceModel = await _channel.invokeMethod('getDeviceModel'); return deviceModel; } } 呼び出すメソッドを引数に指定

Slide 35

Slide 35 text

Androidでハンドリングする ● Dartから MethodChannel.invokeMethod が呼び出される とAndroidではMethodCallHandler.onMethodCallが呼び 出される ● ここでDartで指定したメソッド名が取得できるのでメソッド名 に対応した処理を行い結果を返却する。

Slide 36

Slide 36 text

DevicePlugin.kt class DevicePlugin : MethodCallHandler { companion object { @JvmStatic fun registerWith(registrar: Registrar) { val channel = MethodChannel(registrar.messenger(), "device_plugin") channel.setMethodCallHandler(DevicePlugin()) } } override fun onMethodCall(call: MethodCall, result: Result): Unit { when(call.method) { "getDeviceModel" -> result.success(Build.MODEL) else -> result.notImplemented() } } } DartのMethodChannel.invokeMethodで onMethodCallが呼び出される

Slide 37

Slide 37 text

DevicePlugin.kt class DevicePlugin : MethodCallHandler { companion object { @JvmStatic fun registerWith(registrar: Registrar) { val channel = MethodChannel(registrar.messenger(), "device_plugin") channel.setMethodCallHandler(DevicePlugin()) } } override fun onMethodCall(call: MethodCall, result: Result): Unit { when(call.method) { "getDeviceModel" -> result.success(Build.MODEL) else -> result.notImplemented() } } } call.methodでDartで指定したメ ソッド名が取れる

Slide 38

Slide 38 text

DevicePlugin.kt class DevicePlugin : MethodCallHandler { companion object { @JvmStatic fun registerWith(registrar: Registrar) { val channel = MethodChannel(registrar.messenger(), "device_plugin") channel.setMethodCallHandler(DevicePlugin()) } } override fun onMethodCall(call: MethodCall, result: Result): Unit { when(call.method) { "getDeviceModel" -> result.success(Build.MODEL) else -> result.notImplemented() } } } メソッド名によって処理を変える

Slide 39

Slide 39 text

DevicePlugin.kt class DevicePlugin : MethodCallHandler { companion object { @JvmStatic fun registerWith(registrar: Registrar) { val channel = MethodChannel(registrar.messenger(), "device_plugin") channel.setMethodCallHandler(DevicePlugin()) } } override fun onMethodCall(call: MethodCall, result: Result): Unit { when(call.method) { "getDeviceModel" -> result.success(Build.MODEL) else -> result.notImplemented() } } } result.successの引数にDartに 返す値を指定する

Slide 40

Slide 40 text

DevicePlugin.kt class DevicePlugin : MethodCallHandler { companion object { @JvmStatic fun registerWith(registrar: Registrar) { val channel = MethodChannel(registrar.messenger(), "device_plugin") channel.setMethodCallHandler(DevicePlugin()) } } override fun onMethodCall(call: MethodCall, result: Result): Unit { when(call.method) { "getDeviceModel" -> result.success(Build.MODEL) else -> result.notImplemented() } } } 未実装のメソッド名の場合は notImplementedを呼び出す

Slide 41

Slide 41 text

iOSでハンドリングする ● Dartから MethodChannel.invokeMethod が呼び出される とiOSではFlutterPlugin.handleが呼び出される ● ここでDartで指定したメソッド名が取得できるのでメソッド名 に対応した処理を行い結果を返却する。

Slide 42

Slide 42 text

SwiftDevicePlugin.swift public class SwiftDevicePlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "device_plugin", binaryMessenger: registrar.messenger()) let instance = SwiftDevicePlugin() registrar.addMethodCallDelegate(instance, channel: channel) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case "getDeviceModel": result(UIDevice.current.model) break default: result(FlutterMethodNotImplemented) } } } MethodChannel.invokeMethod でhandleが呼び出される

Slide 43

Slide 43 text

SwiftDevicePlugin.swift public class SwiftDevicePlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "device_plugin", binaryMessenger: registrar.messenger()) let instance = SwiftDevicePlugin() registrar.addMethodCallDelegate(instance, channel: channel) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case "getDeviceModel": result(UIDevice.current.model) break default: result(FlutterMethodNotImplemented) } } } FlutterMethodCall.methodで 指定したメソッド名がとれる

Slide 44

Slide 44 text

SwiftDevicePlugin.swift public class SwiftDevicePlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "device_plugin", binaryMessenger: registrar.messenger()) let instance = SwiftDevicePlugin() registrar.addMethodCallDelegate(instance, channel: channel) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case "getDeviceModel": result(UIDevice.current.model) break default: result(FlutterMethodNotImplemented) } } } getDeviceModelの場合に モデル名を取得してresultに渡す

Slide 45

Slide 45 text

SwiftDevicePlugin.swift public class SwiftDevicePlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "device_plugin", binaryMessenger: registrar.messenger()) let instance = SwiftDevicePlugin() registrar.addMethodCallDelegate(instance, channel: channel) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case "getDeviceModel": result(UIDevice.current.model) break default: result(FlutterMethodNotImplemented) } } } 未実装のメソッド名の場合は FlutterMethodNotImplementedを 返す

Slide 46

Slide 46 text

実装したPluginを使う

Slide 47

Slide 47 text

device_plugin.dart(実装の再確認) class DevicePlugin { static const MethodChannel _channel = const MethodChannel('device_plugin'); static Future get deviceModel async { final String deviceModel = await _channel.invokeMethod('getDeviceModel'); return deviceModel; } } DevicePlugin.deviceModelで デバイスモデルがとれる

Slide 48

Slide 48 text

main.dart class _MyAppState extends State { String _deviceModel = "undefined"; @override void initState() { super.initState(); DevicePlugin.deviceModel.then((deviceModel) { setState(() { _deviceModel = deviceModel; }); }); } //… } デバイスモデルを取得できたら setStateで反映

Slide 49

Slide 49 text

MethodChannelまとめ ● DartからMethodChannel.invokeMethodで各プラットフォー ムの実装を呼び出す ● MethodChannel.invokeMethodが呼び出されると ○ AndroidではMethodCallHandler.onMethodCallが呼び 出される ○ iOSではFlutterPlugin.handleが呼び出される ● 各プラットフォームでメソッド名に対応した処理を行い結果を 返す

Slide 50

Slide 50 text

EventChannel

Slide 51

Slide 51 text

● Android・iOSで発生したイベントを継続的にDart側へ通知 できる。DartでAndroid・iOSのイベントを監視できる。 例えば ● 位置情報の変更を監視したいとき ● 音量の変更を監視したいとき ● センサー情報の変更を監視したいとき EventChannel

Slide 52

Slide 52 text

EventChannel Flutter Android iOS receiveBroadcastStream success, error, endOfStream cancel

Slide 53

Slide 53 text

EventChannel Flutter Android iOS receiveBroadcastStream success, error, endOfStream cancel

Slide 54

Slide 54 text

EventChannel Flutter Android iOS receiveBroadcastStream success, error, endOfStream cancel

Slide 55

Slide 55 text

EventChannel Flutter Android iOS receiveBroadcastStream success, error, endOfStream cancel

Slide 56

Slide 56 text

端末の音量変更時に 音量を通知するPluginを作る Pluginの仕様 ● 各プラットフォームでは端末の音量を監視し、変更があればDart 側に通知する。 ● Dart側では音量の変更を監視できる。

Slide 57

Slide 57 text

Pluginのプロジェクトを作る flutter create --org com.kuwapp --template=plugin -i swift -a kotlin volume_plugin

Slide 58

Slide 58 text

Dartで各プラットフォームで起きた変更を受け 取れるようにする ● 各プラットフォームのイベントを継続的に受け取るために EventChannelを使用する。 ● EventChannelから生成したStreamをlistenする。 ● 自動生成されたvolume_plugin.dartに変更を加える。

Slide 59

Slide 59 text

volume_plugin.dart class VolumePlugin { //省略 シングルトンにしておく final EventChannel _channel = const EventChannel('volume_plugin'); Stream _onVolumeChanged; Stream get onVolumeChanged { if (_onVolumeChanged == null) { _onVolumeChanged = _channel .receiveBroadcastStream() .map((dynamic event) => event as int); } return _onVolumeChanged; } } EventChannelの インスタンス生成

Slide 60

Slide 60 text

volume_plugin.dart class VolumePlugin { //省略 シングルトンにしておく final EventChannel _channel = const EventChannel('volume_plugin_event'); Stream _onVolumeChanged; Stream get onVolumeChanged { if (_onVolumeChanged == null) { _onVolumeChanged = _channel .receiveBroadcastStream() .map((dynamic event) => event as int); } return _onVolumeChanged; } } 各プラットフォームの イベントを受け取る

Slide 61

Slide 61 text

volume_plugin.dart class VolumePlugin { //省略 シングルトンにしておく final EventChannel _channel = const EventChannel('volume_plugin_event'); Stream _onVolumeChanged; Stream get onVolumeChanged { if (_onVolumeChanged == null) { _onVolumeChanged = _channel .receiveBroadcastStream() .map((dynamic event) => event as int); } return _onVolumeChanged; } } Streamは保持する。 新たにStreamを生成すると以前 生成したStreamはCancelされ る。 シングルトンにするのはStreamを 複数生成させないため。

Slide 62

Slide 62 text

volume_plugin.dart class VolumePlugin { static VolumePlugin _instance; factory VolumePlugin() { if (_instance == null) { _instance = VolumePlugin._private(); } return _instance; } VolumePlugin._private();  //略… } シングルトンはこんな感じに

Slide 63

Slide 63 text

Androidでイベントを流す ● PluginクラスにEventChannel.StreamHandlerのonListen とonCancelを実装する ● onListenはDart側でStreamが購読された場合に呼び出さ れるのでイベントを流す処理を開始させる ● onCancelはStreamがキャンセルされた場合に呼び出され るのでイベントの監視を停止する処理を入れる

Slide 64

Slide 64 text

VolumePlugin.kt class VolumePlugin(private val registrar: Registrar) : EventChannel.StreamHandler { companion object { @JvmStatic fun registerWith(registrar: Registrar) { val channel = EventChannel(registrar.messenger(), "volume_plugin") channel.setStreamHandler(VolumePlugin(registrar)) } } //略… } EventChannelのインスタンス生成

Slide 65

Slide 65 text

VolumePlugin.kt class VolumePlugin(private val registrar: Registrar) : EventChannel.StreamHandler { companion object { @JvmStatic fun registerWith(registrar: Registrar) { val channel = EventChannel(registrar.messenger(), "volume_plugin") channel.setStreamHandler(VolumePlugin(registrar)) } } //略… } PluginをsetStreamHandlerの引数にする

Slide 66

Slide 66 text

VolumePlugin.kt class VolumePlugin(private val registrar: Registrar) : EventChannel.StreamHandler { private var volumeChangeReceiver: BroadcastReceiver? = null override fun onListen(argument: Any?, eventSink: EventChannel.EventSink) { volumeChangeReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { eventSink.success(getVolume()) } } val filter = IntentFilter("android.media.VOLUME_CHANGED_ACTION") registrar.context().registerReceiver(volumeChangeReceiver, filter) } override fun onCancel(argument: Any?) { volumeChangeReceiver?.let { registrar.context().unregisterReceiver(it) } } } EventChannel.StreamHandler を実装する

Slide 67

Slide 67 text

VolumePlugin.kt class VolumePlugin(private val registrar: Registrar) : EventChannel.StreamHandler { private var volumeChangeReceiver: BroadcastReceiver? = null override fun onListen(argument: Any?, eventSink: EventChannel.EventSink) { volumeChangeReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { eventSink.success(getVolume()) } } val filter = IntentFilter("android.media.VOLUME_CHANGED_ACTION") registrar.context().registerReceiver(volumeChangeReceiver, filter) } override fun onCancel(argument: Any?) { volumeChangeReceiver?.let { registrar.context().unregisterReceiver(it) } } } dart側でlistenされると呼び出される

Slide 68

Slide 68 text

VolumePlugin.kt class VolumePlugin(private val registrar: Registrar) : EventChannel.StreamHandler { private var volumeChangeReceiver: BroadcastReceiver? = null override fun onListen(argument: Any?, eventSink: EventChannel.EventSink) { volumeChangeReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { eventSink.success(getVolume()) } } val filter = IntentFilter("android.media.VOLUME_CHANGED_ACTION") registrar.context().registerReceiver(volumeChangeReceiver, filter) } override fun onCancel(argument: Any?) { volumeChangeReceiver?.let { registrar.context().unregisterReceiver(it) } } } 音量ボタンの変更の監視を開始する

Slide 69

Slide 69 text

VolumePlugin.kt class VolumePlugin(private val registrar: Registrar) : EventChannel.StreamHandler { private var volumeChangeReceiver: BroadcastReceiver? = null override fun onListen(argument: Any?, eventSink: EventChannel.EventSink) { volumeChangeReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { eventSink.success(getVolume()) } } val filter = IntentFilter("android.media.VOLUME_CHANGED_ACTION") registrar.context().registerReceiver(volumeChangeReceiver, filter) } override fun onCancel(argument: Any?) { volumeChangeReceiver?.let { registrar.context().unregisterReceiver(it) } } } 音量変更時にEventSink.successを呼び出し Dartに通知

Slide 70

Slide 70 text

VolumePlugin.kt class VolumePlugin(private val registrar: Registrar) : EventChannel.StreamHandler { private var volumeChangeReceiver: BroadcastReceiver? = null override fun onListen(argument: Any?, eventSink: EventChannel.EventSink) { volumeChangeReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { eventSink.success(getVolume()) } } val filter = IntentFilter("android.media.VOLUME_CHANGED_ACTION") registrar.context().registerReceiver(volumeChangeReceiver, filter) } override fun onCancel(argument: Any?) { volumeChangeReceiver?.let { registrar.context().unregisterReceiver(it) } } } 音量の変更の監視を停止する

Slide 71

Slide 71 text

iOSでイベントを流す ● PluginクラスにFlutterStreamHandlerのonListenと onCancelを実装する ● onListenはDart側でStreamが購読された場合に呼び出さ れるのでイベントを流す処理を開始させる ● onCancelはStreamがキャンセルされた場合に呼び出され るのでイベントの監視を停止する処理を入れる

Slide 72

Slide 72 text

SwiftVolumePlugin.swift public class SwiftVolumePlugin: NSObject, FlutterPlugin, FlutterStreamHandler { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterEventChannel(name: "volume_plugin", binaryMessenger: registrar.messenger()) let instance = SwiftVolumePlugin() channel.setStreamHandler(instance) } public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { // 音量変更の監視をはじめる  // 音量変更時に events(volume)を呼び出す return nil } public func onCancel(withArguments arguments: Any?) -> FlutterError? { // 音量変更の監視の停止 return nil } } FlutterEventChannelのインスタンスを生成

Slide 73

Slide 73 text

SwiftVolumePlugin.swift public class SwiftVolumePlugin: NSObject, FlutterPlugin, FlutterStreamHandler { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterEventChannel(name: "volume_plugin", binaryMessenger: registrar.messenger()) let instance = SwiftVolumePlugin() channel.setStreamHandler(instance) } public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { // 音量変更の監視をはじめる  // 音量変更時に events(volume)を呼び出す return nil } public func onCancel(withArguments arguments: Any?) -> FlutterError? { // 音量変更の監視の停止 return nil } } setStreamHandlerを呼び出す

Slide 74

Slide 74 text

SwiftVolumePlugin.swift public class SwiftVolumePlugin: NSObject, FlutterPlugin, FlutterStreamHandler { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterEventChannel(name: "volume_plugin", binaryMessenger: registrar.messenger()) let instance = SwiftVolumePlugin() channel.setStreamHandler(instance) } public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { // 音量変更の監視をはじめる  // 音量変更時に events(volume)を呼び出す return nil } public func onCancel(withArguments arguments: Any?) -> FlutterError? { // 音量変更の監視の停止 return nil } } onListen で音量変更時に現在の音量を流すよ うにする(省略)

Slide 75

Slide 75 text

SwiftVolumePlugin.swift public class SwiftVolumePlugin: NSObject, FlutterPlugin, FlutterStreamHandler { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterEventChannel(name: "volume_plugin", binaryMessenger: registrar.messenger()) let instance = SwiftVolumePlugin() channel.setStreamHandler(instance) } public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { // 音量変更の監視をはじめる  // 音量変更時に events(volume)を呼び出す return nil } public func onCancel(withArguments arguments: Any?) -> FlutterError? { // 音量変更の監視の停止 return nil } } onCancel で音量の監視を停止する

Slide 76

Slide 76 text

実装したPluginを使う

Slide 77

Slide 77 text

class _MyAppState extends State { int _volume = 0; StreamSubscription _subscription; @override void initState() { super.initState(); _subscription = VolumePlugin().onVolumeChanged.listen((volume) { setState(() { _volume = volume; }); }); } @override void dispose() { _subscription.cancel(); super.dispose(); } } VolumePlugin.dart 音量の変更を購読する

Slide 78

Slide 78 text

class _MyAppState extends State { int _volume = 0; StreamSubscription _subscription; @override void initState() { super.initState(); _subscription = VolumePlugin().onVolumeChanged.listen((volume) { setState(() { _volume = volume; }); }); } @override void dispose() { _subscription.cancel(); super.dispose(); } } VolumePlugin.dart StreamSubscriptionはCancelで きるように保持しておく

Slide 79

Slide 79 text

class _MyAppState extends State { int _volume = 0; StreamSubscription _subscription; @override void initState() { super.initState(); _subscription = VolumePlugin().onVolumeChanged.listen((volume) { setState(() { _volume = volume; }); }); } @override void dispose() { _subscription.cancel(); super.dispose(); } } VolumePlugin.dart dispose時にcancel

Slide 80

Slide 80 text

EventChannelまとめ ● EventChannelを使うことでAndroid・iOSで起きたイベントを Dartで継続的に監視できる ● onListenでイベントを投げる処理を開始し、onCancelで停 止させる ● AndroidではEventSink.success、iOSでは FlutterEventSinkを呼び出すことでDart側へ通知できる

Slide 81

Slide 81 text

Dartと各プラットフォームで 何をやりとりできるのか

Slide 82

Slide 82 text

MethodCodec

Slide 83

Slide 83 text

MethodCodec ● Dartと各プラットフォーム間でやりとりする値のエンコード・ デコードを行うクラス。 ● DartからAndroid・iOSへ受け渡す値をバイナリにエンコード して渡し、Android・iOSからの結果をデコードしてDartに返 している。 ● StandardMethodCodecとJsonMethodCodecがある ● MethodChannel or EventChannelのコンストラクタに指定 する

Slide 84

Slide 84 text

StandardMethodCodec

Slide 85

Slide 85 text

StandardMethodCodec ● null、bool、String、int、List、Mapなどの型をやりとりすると きに使用する。 ● デフォルトではこちらが使用される。 どの型を使用できるのかは次のスライドに。

Slide 86

Slide 86 text

StandardMethodCodec https://docs.flutter.io/flutter/services/StandardMessageCodec-class.html

Slide 87

Slide 87 text

StandardMethodCodecを使う

Slide 88

Slide 88 text

Dart実装

Slide 89

Slide 89 text

Dart実装 static const MethodChannel _channel = const MethodChannel('methodcodec_plugin', StandardMethodCodec()); static Future toUpperCase(String str) async { final String result = await _channel.invokeMethod('toUpperCase', str); return result; } MethodChannelのコンストラクタに StandardMethodCodecを指定

Slide 90

Slide 90 text

Dart実装 static const MethodChannel _channel = const MethodChannel('methodcodec_plugin', StandardMethodCodec()); static Future toUpperCase(String str) async { final String result = await _channel.invokeMethod('toUpperCase', str); return result; } invokeMethodに引数を指定

Slide 91

Slide 91 text

Android実装

Slide 92

Slide 92 text

Android class MethodcodecPlugin(): MethodCallHandler { companion object { @JvmStatic fun registerWith(registrar: Registrar): Unit { val channel = MethodChannel(registrar.messenger(), "methodcodec_plugin", StandardMethodCodec.INSTANCE) channel.setMethodCallHandler(MethodcodecPlugin()) } } override fun onMethodCall(call: MethodCall, result: Result): Unit { if (call.method == "toUpperCase") { result.success((call.arguments as String).toUpperCase()) } else { result.notImplemented() } } } StandardMethodCodecを指定

Slide 93

Slide 93 text

Android class MethodcodecPlugin(): MethodCallHandler { companion object { @JvmStatic fun registerWith(registrar: Registrar): Unit { val channel = MethodChannel(registrar.messenger(), "methodcodec_plugin", StandardMethodCodec.INSTANCE) channel.setMethodCallHandler(MethodcodecPlugin()) } } override fun onMethodCall(call: MethodCall, result: Result): Unit { if (call.method == "toUpperCase") { result.success((call.arguments as String).toUpperCase()) } else { result.notImplemented() } } } 引数を受け取る

Slide 94

Slide 94 text

iOSはほぼ変わらないので省略

Slide 95

Slide 95 text

JsonMethodCodec

Slide 96

Slide 96 text

JsonMethodCodec ● Json形式でDartと各プラットフォーム間で値をやりとりする ときに使用する ● Androidではorg.jsonパッケージのクラス(JSONObject、 JSONArray等)になり、iOSではNSJSONSerializationにな る 使える型等は次のスライド

Slide 97

Slide 97 text

JsonMethodCodec https://docs.flutter.io/flutter/services/JSONMessageCodec-class.html

Slide 98

Slide 98 text

JsonMethodCodecを使う

Slide 99

Slide 99 text

Dart実装

Slide 100

Slide 100 text

Dart実装 class JsonmethodcodecTestPlugin { static const MethodChannel _channel = const MethodChannel('jsonmethodcodec_test_plugin', JSONMethodCodec()); static void setUser() { _channel.invokeMethod("setUser", { "user": {"id": 14, "name": "Tom", "age": 35} }); } } JSONMethodCodecを指定する

Slide 101

Slide 101 text

Dart実装 class JsonmethodcodecTestPlugin { static const MethodChannel _channel = const MethodChannel('jsonmethodcodec_test_plugin', JSONMethodCodec()); static void setUser() { _channel.invokeMethod("setUser", { "user": {"id": 14, "name": "Tom", "age": 35} }); } } 引数にJsonライクなものを渡す 内部的に文字列に変換されている

Slide 102

Slide 102 text

Android実装

Slide 103

Slide 103 text

Android実装 class JsonmethodcodecTestPlugin : MethodCallHandler { companion object { @JvmStatic fun registerWith(registrar: Registrar) { val channel = MethodChannel(registrar.messenger(), "jsonmethodcodec_test_plugin", JSONMethodCodec.INSTANCE) channel.setMethodCallHandler(JsonmethodcodecTestPlugin()) } } override fun onMethodCall(call: MethodCall, result: Result) { if (call.method == "setUser") { val json = call.arguments as JSONObject val user = json.getJSONObject("user") val id = user.getInt("id") val name = user.getString("name") val age = user.getInt("age") result.success(null) } JSONMethodCocdecを指定

Slide 104

Slide 104 text

Android実装 class JsonmethodcodecTestPlugin : MethodCallHandler { companion object { @JvmStatic fun registerWith(registrar: Registrar) { val channel = MethodChannel(registrar.messenger(), "jsonmethodcodec_test_plugin", JSONMethodCodec.INSTANCE) channel.setMethodCallHandler(JsonmethodcodecTestPlugin()) } } override fun onMethodCall(call: MethodCall, result: Result) { if (call.method == "setUser") { val json = call.arguments as JSONObject val user = json.getJSONObject("user") val id = user.getInt("id") val name = user.getString("name") val age = user.getInt("age") result.success(null) } JSONObjectで取れる

Slide 105

Slide 105 text

Android実装 class JsonmethodcodecTestPlugin : MethodCallHandler { companion object { @JvmStatic fun registerWith(registrar: Registrar) { val channel = MethodChannel(registrar.messenger(), "jsonmethodcodec_test_plugin", JSONMethodCodec.INSTANCE) channel.setMethodCallHandler(JsonmethodcodecTestPlugin()) } } override fun onMethodCall(call: MethodCall, result: Result) { if (call.method == "setUser") { val json = call.arguments as JSONObject val user = json.getJSONObject("user") val id = user.getInt("id") val name = user.getString("name") val age = user.getInt("age") result.success(null) } JSONObjectから Jsonの要素を取得 する

Slide 106

Slide 106 text

iOSはほぼ変わらないので省略

Slide 107

Slide 107 text

MethodCodecまとめ ● MethodCodecでDartと各プラットフォーム間でやりとりする 値のエンコード・デコードを行う ● StandardMethodCodecは基本的な型をやりとりする場合 に使用する。 ● JsonMethodCodecはJsonでやりとりする場合に使用す る。 ● デフォルトではStandardMethodCodecが使われる。

Slide 108

Slide 108 text

TIPS

Slide 109

Slide 109 text

PluginでAndroidの onActivityResultがとりたい

Slide 110

Slide 110 text

onActivityResultをハンドリングする class EventHandlingPlugin : MethodCallHandler, PluginRegistry.ActivityResultListener { companion object { @JvmStatic fun registerWith(registrar: Registrar) { val channel = MethodChannel(registrar.messenger(), "event_handling") val plugin = EventHandlingPlugin() channel.setMethodCallHandler(plugin) registrar.addActivityResultListener(plugin) } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { // TODO return false } }

Slide 111

Slide 111 text

onActivityResultをハンドリングする class EventHandlingPlugin : MethodCallHandler, PluginRegistry.ActivityResultListener { companion object { @JvmStatic fun registerWith(registrar: Registrar) { val channel = MethodChannel(registrar.messenger(), "event_handling") val plugin = EventHandlingPlugin() channel.setMethodCallHandler(plugin) registrar.addActivityResultListener(plugin) } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { // TODO return false } }

Slide 112

Slide 112 text

onActivityResultをハンドリングする class EventHandlingPlugin : MethodCallHandler, PluginRegistry.ActivityResultListener { companion object { @JvmStatic fun registerWith(registrar: Registrar) { val channel = MethodChannel(registrar.messenger(), "event_handling") val plugin = EventHandlingPlugin() channel.setMethodCallHandler(plugin) registrar.addActivityResultListener(plugin) } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { // TODO return false } }

Slide 113

Slide 113 text

他にも ● PluginRegistry.Registrar.addRequestPermissionsResultListener ● PluginRegistry.Registrar.addNewIntentListener ● PluginRegistry.Registrar.addUserLeaveHintListener

Slide 114

Slide 114 text

EventChannelを使わずに 各プラットフォームから Dartを呼び出す

Slide 115

Slide 115 text

MethodChannelでDartを呼び出す ● Dartから各プラットフォームの実装を呼び出すには MethodChannel.invokeMethodを使用していたが、その逆 も可能。 ● 各プラットフォーム側でMethodChannel.invokeMethodを 呼び出し、Dartでハンドリングする。 ● 継続的に通知しないものはEventChannelではなくこちらを 使う方が良さそう。

Slide 116

Slide 116 text

Androidの実装 class MethodChannelTestPlugin(private val channel: MethodChannel): MethodCallHandler { companion object { @JvmStatic fun registerWith(registrar: Registrar): Unit { val channel = MethodChannel(registrar.messenger(), "method_channel_test") channel.setMethodCallHandler(MethodChannelTestPlugin(channel)) } } // 何かのトークン更新時に呼び出されるとする fun onTokenRefreshed(newToken: String) { channel.invokeMethod("onTokenRefreshed", newToken) }

Slide 117

Slide 117 text

Dartの実装 static MethodChannel _channel = MethodChannel('method_channel_test') ..setMethodCallHandler(_handle); static Future _handle(MethodCall methodCall) { if (methodCall.method == "onTokenRefreshed") { String newToken = methodCall.arguments; } return null; }

Slide 118

Slide 118 text

Dartの実装 static MethodChannel _channel = MethodChannel('method_channel_test') ..setMethodCallHandler(_handle); static Future _handle(MethodCall methodCall) { if (methodCall.method == "onTokenRefreshed") { String newToken = methodCall.arguments; } return null; }

Slide 119

Slide 119 text

Dartの実装 static MethodChannel _channel = MethodChannel('method_channel_test') ..setMethodCallHandler(_handle); static Future _handle(MethodCall methodCall) { if (methodCall.method == "onTokenRefreshed") { String newToken = methodCall.arguments; } return null; }

Slide 120

Slide 120 text

まとめ

Slide 121

Slide 121 text

まとめ ● FlutterPluginとはiOS・Androidの実装とそれらを使用する ためのDartの実装を含むパッケージのこと。 ● MethodChannelで各プラットフォームのAPIを呼び出し結果 を受け取ることができる。 ● EventChannelで各プラットフォームのイベントを監視でき る。 ● Dartと各プラットフォームでやりとりする値によって StandardMethodCodecとJsonMethodCodecを使い分け る。

Slide 122

Slide 122 text

ありがとうございました