FlutterでシステムのライフサイクルをSwiftで

## はじめに

ここ最近はFlutterをやっていて、業務でSwiftでプログラミングはほとんどやっていないですが、

やっぱり`if-let`とか恋しくなります。（アンラップの処理がDartめんどいというか自作しないといけてない）

そんなこんなですが、ネイティブ側とやりとりすることもあり、Flutterで書いてたSwiftの部分を備忘録的に載せていきます。


## AppDelegateでアプリのライフサイクルを通知

Flutterをやっていると言ってもまだ4ヶ月くらい[(Flutter開発を4ヶ月ほどやっての振り返り)](https://qiita.com/repepe2en/items/d30a545df23a03d32cca)なのでわからないことだらけです。

Flutterでもライフサイクルの概念はあり、全く同じではないのですがモバイル開発をしていればそんなに問題はないのですが、画面のライフサイクルというよりアプリ全体のシステムのライフサイクルで苦戦しました。

アプリ復帰時、アプリをバックグラウンドに移行したときにハンドリングできるライフサイクルメソッドは提供されていますが、僕たちのやり方が良くないのかその画面のStateが生きているときしかやってくれないような動きでした。
なので、下タブありのアプリだったんですが、Aという画面で実装してもBという画面で実装していなければ、Bの画面でバックグラウンドに移行すると処理に入ってくれませんでした。

なので、常に生きているというかAppDelegateのようなアプリを管理する画面を下に設定して、システムのライフサイクルはネイティブのを利用しました。

### EventChannelを利用

main.dartがAppDelegateのようなものでそこでやるのが普通なのかなと思うのですが、root.dartという画面でハンドリングしました。

やり方としてはEvent Channelというものを利用してアプリ側から`applicationDidBecomeActive`などでFlutter側に処理に入ったことを通知してあげる感じです。

```swift

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というのを使うと通知ができます。

ここでは

```swift
override func applicationWillEnterForeground(_ application: UIApplication) {
        // flutter側にアプリ復帰を通知
        self.eventSink?("onResume")
    }
```

のようにStringでFlutter側でライフサイクルの種類を通知しています。
めんどくさがらずにenumとかに定義してやりとりしたほうが良いと思いますが、、
AndroidやFlutterのonResumeのタイミングとも厳密には違いますが、applicationWillEnterForeground
で送っています。


## 最後に

最近個人でも触れてないですが、なんとかSwiftの記事を書こうとしたものの無情にも時間はいつの間にかすぎていき結局ギリギリで今考えつくものを投稿する形になってしまいました。。

SwiftっちゃSwiftだけど！みたいなご指摘あると思うのですが優しい目で見てくださると嬉しいです（白目）