Flutter(iOS)でアプリkillを検知して処理したい

iOSのアプリをFlutterでコーディング中にダブルタップからのフリックでアプリをキルするタイミングに処理を施したいなんて時に、色々調べて試しました。
Flutter(Dart)〜プラットフォーム(Swift)間でのやり取りから学習し、最終的にはFlutterのみで解決できることが発覚💦
## Swift(プラットフォーム)〜Dart(Flutter)間での呼び出しのタイミングとメソッドの操作
```ios/Runner/AppDelegate.swift
import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    // Flutter側からメソッドを検知する
    let _controller :FlutterViewController = window?.rootViewController as! FlutterViewController
    let _channel = FlutterMethodChannel(name: "test_hoge", binaryMessenger: _controller.binaryMessenger)
    _channel.setMethodCallHandler({
      (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
        switch call.method {
          // Flutter側のコードを取得できたらネイティブメソッドを呼び出す
        case "getHoge":
          self.receiveWillGetHoge(result: result,_controller: _controller)
          // 引数がある場合はcall.argumentsから取得
          return
        default:
          result(FlutterMethodNotImplemented)
          return
        }
        // Flutterへの戻り値はresult()で返す
    })
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
  // ネイティブ(Swift)側で呼び出すメソッド
  func receiveWillGetHoge(result: FlutterResult,_controller: FlutterViewController) {
    print("FlutterのGET HOGE!!ボタンが押されました")
  }
  // フリックしてアプリを終了させた時に５秒間呼ばれ、その後アプリが破棄される
  override func applicationWillTerminate(_ application: UIApplication) {
    self.window.isHidden = true;
    print("applicationWillTerminate start")
    // Flutter側のメソッドを呼び出したい時はinvokeMethodを利用iOS側では第２引数が省略できない為nilで対応
    let _controller :FlutterViewController = window?.rootViewController as! FlutterViewController
    let _channel = FlutterMethodChannel(name: "test_hoge", binaryMessenger: _controller.binaryMessenger)
    _channel.invokeMethod("getHoge", arguments: nil)
    sleep(5)
    print("applicationWillTerminate end")
  }
  // ダブルタップ後にアプリを保留にした時に呼ばれる
  override func applicationWillResignActive(_ application: UIApplication) {
    print("applicationWillResignActive")
    self.window.isHidden = false;   
  }
  // アプリを開いた時に呼ばれる
  override func applicationDidBecomeActive(_ application: UIApplication) {
    print("applicationDidBecomeActive")
    self.window.isHidden = false;
  }
  // 保留中のアプリを選択してアプリに入った時に呼ばれる
  override func applicationWillEnterForeground(_ application: UIApplication) {
    print("applicationWillEnterForeground")
    self.window.isHidden = false;   
  }
  // アプリは保留中だがタップしてホームに戻った時に呼ばれる
  override func applicationDidEnterBackground(_ application: UIApplication) {
    print("applicationDidEnterBackground")
    self.window.isHidden = false;   
  }
}
// AndroidであればKotlinでの実装になりますので別の記事を参照下さい。
```
## main.dartでは、Swift側に送るメソッドとFlutter側で受け取るメソッドを記述
```lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

class TestView extends HookConsumerWidget {
  const TestView({Key? key}) : super(key: key);
  // FlutterとSwiftを結ぶ為のMethodChannelAPI
  static const platform = MethodChannel('test_hoge');

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Swiftに送るメソッド
    Future<void> _getHoge() async {
      String willGetHoge;
      try {
        //Swiftに'getHoge'メッセージを送る
        await platform.invokeMethod('getHoge');
      } on PlatformException catch (e) {
        willGetHoge = "Failed to get hoge";
      }
    }
    // Flutter側で受け取る
    platform.setMethodCallHandler((MethodCall call) {
    switch (call.method) {
      case 'getHoge':
        debugPrint('ネイティブ側のメソッドを取得');
        // 引数がある場合はcall.argumentsから取得 
        break;
      default:
        debugPrint('');
        break;
    } throw Exception('');
  });

    return Scaffold(
        body: Center(
            child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  // ボタンを押して'getHoge'を送信　プラットフォーム側のメソッドが呼ばれる
          ElevatedButton(
            child: const Text('GET HOGE!!'),
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.blueAccent,
              foregroundColor: Colors.white,
            ),
            onPressed: () => _getHoge(),
          )
        ])));
  }
}
```
とまあ、上記の要領でやりとりをする手段もあるのですが正確に言えばアプリがkillしたタイミングがわかればいいのでもっと他にあるのではと調べていましたら、`AppLifecycleState`を発見！

`WidgetsBindingObserver`を利用して`didChangeAppLifecycleState`メソッドでアプリのライフサイクルを検知するすることができるらしく挑戦してみるものの、**paused**や**resumed**は取得できたが`Hooks`を使っているコードだからか全ての状態を上手く取得することができなかった。調べていると`useAppLifecycleState`で通知している記事を発見！

ドキュメントを辿っていくと、`useOnAppLifecycleStateChange`に辿り着いた！

# [useOnAppLifecycleStateChange](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useOnAppLifecycleStateChange.html)でアプリKILLのタイミングを検知
```lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

class TestView extends HookConsumerWidget {
  const TestView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // if文での処理
    useOnAppLifecycleStateChange((beforeState, afterState) async {
      if (afterState == AppLifecycleState.detached) {
        // 今回目標としていたアプリがKILLされたタイミング！ここに処理を書く。
        debugPrint('保留状態からフリックしてアプリをKILL(破棄)したタイミング');
      }
    });

    // switch文での処理
    useOnAppLifecycleStateChange((_, state) {
      switch (state) {
        case AppLifecycleState.resumed:
          debugPrint('ホームからダブルタップして保留中のアプリを再開したタイミング');
          break;
        case AppLifecycleState.inactive:
          debugPrint('ダブルタップでアプリ保留(バックグラウンド状態)したタイミング');
          break;
        case AppLifecycleState.paused:
          debugPrint('ワンタップしてホームに戻った状態(一時停止状態)の時に呼ばれる');
          break;
        case AppLifecycleState.detached:
          // 上記と同じ
          break;
      }
    });

    return Scaffold(
        body: Center(
            child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: const <Widget>[
                 Text("test")
        ])));
  }
}
```