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

FlutterPluginの作り方

Avatar for kuwapp kuwapp
September 01, 2018

 FlutterPluginの作り方

GDGDevFestTokyo 2018

Avatar for kuwapp

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; }