Flutterアプリのライフサイクル


# はじめに

Flutterアプリのライフサイクルについてまとめています。Flutterのライフサイクルというと、

 * アプリ (`AppLifecycleState`)　←　今回の内容
 * 画面 (`StatefulWidget`)

の2種類がありますが、今回は上のアプリ自体のライフサイクルについての内容です。

`StatefulWidget`のライフサイクルについては、[Flutter StatefulWidgetのライフサイクル](https://qiita.com/kurun_pan/items/116288b8ab2c409d2ee5)にまとめていますので、参照してください。

# アプリのライフサイクル一覧とプラットフォームの対応関係

`AppLifecycleState`の状態遷移と状態一覧を以下に示します。
<img width="409" alt="スクリーンショット 2020-02-17 20.02.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/23b700eb-cdbb-1e3b-b20e-71dc46340202.png">

基本的にプラットフォーム側の状態をFlutterアプリの状態として再定義している感じですが、iOSのライフサイクルに近いと思います。なお、iOSのライフサイクルは詳しくなく、[AndroidエンジニアのためのiOSのUIViewControllerのライフサイクルとAndroidのActivityのライフサイクル比較](https://qiita.com/kgmyshin/items/b54e84d71b3406cd6e79)を参考にさせていただきました。間違いがあればコメントでご指摘いただけると幸いです。

`inactive`と`paused`の違いは、画面が表示されているか否かの違いの様子です。

|状態|内容|Android|iOS|
|---|---|---|---|
|inactive|アプリは表示されているが、フォーカスがあたっていない状態|onStart, onPause|viewDidLoad|
|paused|アプリがバックグラウンドに遷移し（最前面に表示されてない）、入力不可な一時停止状態|onPause|viewWillDisappear viewDidDisappear|
|resumed|アプリがフォアグランドに遷移し（paused状態から復帰）、復帰処理用の状態|onResume|viewWillAppear viewDidAppear|
|detached|アプリが終了する時に通る終了処理用の状態|onDestroy|dealloc|

# AndroidのonSaveInstanceStateの様な仕組みはあるのか？

現状なさそうです。
`AppLifecycleState`の状態を見て、必要であれば自分で対応する必要があります。

# 状態遷移ユースケース

いくつかのユースケースで状態遷移の動作を確認してみました。

[WidgetsBindingObserver](https://api.flutter.dev/flutter/widgets/WidgetsBindingObserver-class.html)を利用すると[AppLifecycleState](https://api.flutter.dev/flutter/dart-ui/AppLifecycleState-class.html)の状態が取得出来ます。
その他、SystemChannels.lifecycle (詳細は[こちら](https://qiita.com/kurun_pan/items/c0c881f7a3f95ecb3f9d#lifecycle)にまとめてます。) を利用しても取得可能です。

# 確認のためのソースコード

```Dart:
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {

  AppLifecycleState _state;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    print('state = $state');
  }
```

## アプリ起動
イベント発生なし

## 画面回転
イベント発生なし

## アプリ終了
```shell:
I/flutter (13730): state = AppLifecycleState.inactive
I/flutter (13730): state = AppLifecycleState.paused
I/flutter (13730): state = AppLifecycleState.detached
```

## ホームボタンを押す
```shell:
I/flutter (13730): state = AppLifecycleState.inactive
I/flutter (13730): state = AppLifecycleState.paused
```

## 画面OFF
```shell:
I/flutter (15366): state = AppLifecycleState.inactive
I/flutter (15366): state = AppLifecycleState.paused
```

## アプリを履歴から復帰
```shell:
I/flutter (13730): state = AppLifecycleState.resumed
```

## 画面分割で起動
```shell:
I/flutter (13252): state = AppLifecycleState.inactive
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/c34bc8b0-580a-64af-ca3d-b509544f21b8.png" width="240">

## 画面分割状態でサブ画面 (下画面) のアプリを終了
```shell:
I/flutter (13252): state = AppLifecycleState.inactive
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/cf23a897-71dc-24b1-0a5a-854e4b82488f.png" width="240">

## 画面分割状態でサブ画面 (下画面) で新しいアプリを選択して、画面を2分割に戻る
```shell:
I/flutter (13252): state = AppLifecycleState.resumed
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/624dbd44-a149-141f-36d3-01b0a505c5d5.png" width="240">

# 参考文献

* [Flutter App Lifecycle](https://medium.com/pharos-production/flutter-app-lifecycle-4b0ab4a4211a)
* [Flutter Lifecycle for Android and iOS Developers](https://medium.com/flutter-community/flutter-lifecycle-for-android-and-ios-developers-8f532307e0c7)