[Flutter] WidgetsBindingとは何か？

# はじめに

Flutterアプリを動かすためにコールするお決まりの`runApp()`を呼び出す前に、たまに利用することがある

```dart
WidgetsFlutterBinding.ensureInitialized();
```

について、そもそも何よっていうレベルから、なぜ必要なのかの仕組み的な部分などについてまとめています。

## WidgetsFlutterBinding.ensureInitialized() が必要なシーン

大抵の場合は不要ですが、一言で言うと`runApp()`を呼び出す前に`Flutter Engine`の機能を利用したい場合にコールします。

`Flutter Engine`の機能とは、前述のプラットフォーム (Android, iOSなど) の画面の向きの設定やロケールなどです。利用しているプラグインによっては、`runApp()`の前になんらか動作しているとこの設定が事前に必要になります。

## runApp メソッドの中身

[runApp()](https://github.com/flutter/flutter/blob/51fa704642774c6b6ae64c91642b62801e233e1b/packages/flutter/lib/src/widgets/binding.dart#L1053) のソースコードは以下の様になっており、内部で`WidgetsFlutterBinding.ensureInitialized()`メソッドをコールし、`WidgetsFlutterBinding`クラスのインスタンスを生成します。

```dart:packages/flutter/lib/src/widgets/binding.dart
void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}
```

先に言っておくと、全体的な構成は以下の様な感じになっています。
![スクリーンショット 2020-09-20 23.17.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/c0995fc2-b73c-5f2e-fe2d-8a731b1cec4e.png)

## WidgetsFlutterBinding とは？

[ソースコード](https://github.com/flutter/flutter/blob/51fa704642774c6b6ae64c91642b62801e233e1b/packages/flutter/lib/src/widgets/binding.dart#L1255)を見ると`ensureInitialized()`メソッドのみ存在し、`WidgetsBinding`インスタンスを生成するためのものであることが分かります。

```dart:packages/flutter/lib/src/widgets/binding.dart
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

  /// Returns an instance of the [WidgetsBinding], creating and
  /// initializing it if necessary. If one is created, it will be a
  /// [WidgetsFlutterBinding]. If one was previously initialized, then
  /// it will at least implement [WidgetsBinding].
  ///
  /// You only need to call this method if you need the binding to be
  /// initialized before calling [runApp].
  ///
  /// In the `flutter_test` framework, [testWidgets] initializes the
  /// binding instance to a [TestWidgetsFlutterBinding], not a
  /// [WidgetsFlutterBinding].
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance!;
  }
}
```

## WidgetsBinding とは？

今回の記事の本命である、[WidgetsBinding](https://api.flutter.dev/flutter/widgets/WidgetsBinding-mixin.html)について説明していきます。

公式には以下の様に記載がありますが、良く分かりませんね。

```text
The glue between the widgets layer and the Flutter engine.
```

と言うことで、[ソースコード](https://github.com/flutter/flutter/blob/51fa704642774c6b6ae64c91642b62801e233e1b/packages/flutter/lib/src/widgets/binding.dart#L269)を見てみると以下の様になっており、[SystemChannels](https://api.flutter.dev/flutter/services/SystemChannels-class.html)を利用して Flutter Engine と通信 (MethodChannel) の設定をしていることが分かります (他にもロケールのシステムの設定変更のコールバックなども)。

SystemChannelについては、[Flutter プラットフォーム固有機能を利用するためのSystemChannels APIについて](https://qiita.com/kurun_pan/items/c0c881f7a3f95ecb3f9d)にまとめていますので、良かったら読んでみて下さい。

```dart:flutter/lib/src/widgets/binding.dart
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;

    assert(() {
      _debugAddStackFilters();
      return true;
    }());

    // Initialization of [_buildOwner] has to be done after
    // [super.initInstances] is called, as it requires [ServicesBinding] to
    // properly setup the [defaultBinaryMessenger] instance.
    _buildOwner = BuildOwner();
    buildOwner!.onBuildScheduled = _handleBuildScheduled;
    window.onLocaleChanged = handleLocaleChanged;
    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
  }
```

とりあえず分かることは、Flutter Engine と Flutter Framework (Flutter Dartソースコード) をバイディング (通信できる様にしている) だけっぽいと言うことが分かります。

と言うことで、これをアプリ実行の前に行うことで、Flutter Engineと通信できる様になると言うことが分かりました。

ここまで来ると、`runApp`の前にFlutterアプリの機能を利用する場合には`WidgetsFlutterBinding.ensureInitialized()`をコールする必要があることを理解する事ができます。

## WidgetsBinding の具体的な使用例

WidgetsBinding は [WidgetsBindingObserver](https://api.flutter.dev/flutter/widgets/WidgetsBindingObserver-class.html) で利用されることが多そうです。つまり、アプリのライフライクルを取得する時に利用します。これについては、[Flutterアプリのライフサイクル](https://qiita.com/kurun_pan/items/0c6de1313844a8cc1603)にまとめていますので、興味があったら読んで下さい。

```dart:WidgetsBindingの使用例
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');
  }
```