はじめに
ここ最近はFlutterをやっていて、業務でSwiftでプログラミングはほとんどやっていないですが、
やっぱりif-let
とか恋しくなります。(アンラップの処理がDartめんどいというか自作しないといけてない)
そんなこんなですが、ネイティブ側とやりとりすることもあり、Flutterで書いてたSwiftの部分を備忘録的に載せていきます。
AppDelegateでアプリのライフサイクルを通知
Flutterをやっていると言ってもまだ4ヶ月くらい(Flutter開発を4ヶ月ほどやっての振り返り)なのでわからないことだらけです。
Flutterでもライフサイクルの概念はあり、全く同じではないのですがモバイル開発をしていればそんなに問題はないのですが、画面のライフサイクルというよりアプリ全体のシステムのライフサイクルで苦戦しました。
アプリ復帰時、アプリをバックグラウンドに移行したときにハンドリングできるライフサイクルメソッドは提供されていますが、僕たちのやり方が良くないのかその画面のStateが生きているときしかやってくれないような動きでした。
なので、下タブありのアプリだったんですが、Aという画面で実装してもBという画面で実装していなければ、Bの画面でバックグラウンドに移行すると処理に入ってくれませんでした。
なので、常に生きているというかAppDelegateのようなアプリを管理する画面を下に設定して、システムのライフサイクルはネイティブのを利用しました。
EventChannelを利用
main.dartがAppDelegateのようなものでそこでやるのが普通なのかなと思うのですが、root.dartという画面でハンドリングしました。
やり方としてはEvent Channelというものを利用してアプリ側からapplicationDidBecomeActive
などでFlutter側に処理に入ったことを通知してあげる感じです。
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
var eventSink: FlutterEventSink?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
guard let controller: FlutterViewController = window?.rootViewController as? FlutterViewController else {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
let methodChannel = FlutterMethodChannel(name: "jp.co.hoge/nativeStorage",
binaryMessenger: controller.binaryMessenger)
let eventChannel = FlutterEventChannel(name: "jp.co.hoge/nativeLifeCycle", binaryMessenger: controller.binaryMessenger)
eventChannel.setStreamHandler(self)
methodChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
if call.method == "getUserLocal" {
self.getUserLocal(result: result)
return
} else if call.method == "getUsers" {
self.getUsers(result: result)
return
} else {
result(FlutterMethodNotImplemented)
return
}
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// アプリ起動時と復帰時に呼ばれる(didFinishLaunchingWithOptions, applicationWillEnterForegroundの後続処理)
override func applicationDidBecomeActive(_ application: UIApplication) {
self.clearNotificationBadges(application)
}
override func applicationWillEnterForeground(_ application: UIApplication) {
// flutter側にアプリ復帰を通知
self.eventSink?("onResume")
}
override func applicationDidEnterBackground(_ application: UIApplication) {
// flutter側にアプリ復帰を通知
self.eventSink?("onPause")
}
private func getUsers(result: FlutterResult) {
guard let users = UserDefaults.standard.string(forKey: "users") else {
result("")
return
}
result(users)
}
private func getUserLocal(result: FlutterResult) {
guard let userLocal = UserDefaults.standard.string(forKey: "userLocal") else {
result("")
return
}
result(userLocal)
}
// 通知バッジ削除
private func clearNotificationBadges(_ application: UIApplication) {
application.applicationIconBadgeNumber = 0
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
}
}
extension AppDelegate: FlutterStreamHandler {
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
// iOSでchannel設定成功であることを通知
// Flutter側で設定成功通知を受け取っての処理は行っていない
self.eventSink = events
self.eventSink?("iOS setStreamHandler Success")
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
return nil
}
}
Method Channelとかとも混ざってしまっていますが、こんな感じです。
具体的な説明は省きますが、AppDelgateにFlutterStreamHandlerを継承してEventSinkというのを使うと通知ができます。
ここでは
override func applicationWillEnterForeground(_ application: UIApplication) {
// flutter側にアプリ復帰を通知
self.eventSink?("onResume")
}
のようにStringでFlutter側でライフサイクルの種類を通知しています。
めんどくさがらずにenumとかに定義してやりとりしたほうが良いと思いますが、、
AndroidやFlutterのonResumeのタイミングとも厳密には違いますが、applicationWillEnterForeground
で送っています。
最後に
最近個人でも触れてないですが、なんとかSwiftの記事を書こうとしたものの無情にも時間はいつの間にかすぎていき結局ギリギリで今考えつくものを投稿する形になってしまいました。。
SwiftっちゃSwiftだけど!みたいなご指摘あると思うのですが優しい目で見てくださると嬉しいです(白目)