Upgrade to Pro — share decks privately, control downloads, hide ads and more …

FlutterPluginの作り方

kuwapp
September 01, 2018

 FlutterPluginの作り方

GDGDevFestTokyo 2018

kuwapp

September 01, 2018
Tweet

More Decks by kuwapp

Other Decks in Programming

Transcript

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

    device_plugin iOS・Androidの言語指定 デフォルトはObjCとJava
  2. device_plugin.dart class DevicePlugin { static const MethodChannel _channel = const

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

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

    MethodChannel('device_plugin'); static Future<String> get deviceModel async { final String deviceModel = await _channel.invokeMethod('getDeviceModel'); return deviceModel; } } 呼び出すメソッドを引数に指定
  5. 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が呼び出される
  6. 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で指定したメ ソッド名が取れる
  7. 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() } } } メソッド名によって処理を変える
  8. 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に 返す値を指定する
  9. 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を呼び出す
  10. 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が呼び出される
  11. 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で 指定したメソッド名がとれる
  12. 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に渡す
  13. 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を 返す
  14. device_plugin.dart(実装の再確認) class DevicePlugin { static const MethodChannel _channel = const

    MethodChannel('device_plugin'); static Future<String> get deviceModel async { final String deviceModel = await _channel.invokeMethod('getDeviceModel'); return deviceModel; } } DevicePlugin.deviceModelで デバイスモデルがとれる
  15. main.dart class _MyAppState extends State<MyApp> { String _deviceModel = "undefined";

    @override void initState() { super.initState(); DevicePlugin.deviceModel.then((deviceModel) { setState(() { _deviceModel = deviceModel; }); }); } //… } デバイスモデルを取得できたら setStateで反映
  16. volume_plugin.dart class VolumePlugin { //省略 シングルトンにしておく final EventChannel _channel =

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

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

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

    if (_instance == null) { _instance = VolumePlugin._private(); } return _instance; } VolumePlugin._private();  //略… } シングルトンはこんな感じに
  20. 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のインスタンス生成
  21. 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の引数にする
  22. 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 を実装する
  23. 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されると呼び出される
  24. 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) } } } 音量ボタンの変更の監視を開始する
  25. 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に通知
  26. 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) } } } 音量の変更の監視を停止する
  27. 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のインスタンスを生成
  28. 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を呼び出す
  29. 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 で音量変更時に現在の音量を流すよ うにする(省略)
  30. 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 で音量の監視を停止する
  31. class _MyAppState extends State<MyApp> { int _volume = 0; StreamSubscription<int>

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

    _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で きるように保持しておく
  33. class _MyAppState extends State<MyApp> { int _volume = 0; StreamSubscription<int>

    _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
  34. Dart実装 static const MethodChannel _channel = const MethodChannel('methodcodec_plugin', StandardMethodCodec()); static

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

    Future<String> toUpperCase(String str) async { final String result = await _channel.invokeMethod('toUpperCase', str); return result; } invokeMethodに引数を指定
  36. 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を指定
  37. 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() } } } 引数を受け取る
  38. 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を指定する
  39. 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ライクなものを渡す 内部的に文字列に変換されている
  40. 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を指定
  41. 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で取れる
  42. 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の要素を取得 する
  43. 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 } }
  44. 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 } }
  45. 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 } }
  46. 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) }
  47. Dartの実装 static MethodChannel _channel = MethodChannel('method_channel_test') ..setMethodCallHandler(_handle); static Future<Null> _handle(MethodCall

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

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

    methodCall) { if (methodCall.method == "onTokenRefreshed") { String newToken = methodCall.arguments; } return null; }