2020/12/19 : change_notifier.dartの説明の訳

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

Listenable

リスナーのリストを維持するオブジェクトです。

リスナーは特に「オブジェクトが更新されたこと」をクライアントに通知するために使用されます。

結局クライアントとはウィジェット(UI)のこと。リスナーは、そのウィジェット(UI)に変更を通知する。

このインターフェースには二つの種類があります。

  • [ValueListenable]、[Listenable]の現在値のコンセプトを増強するインターフェース。
  • [Animation]、方向のコンセプト(順方向、逆方向)を追加するために、[ValueListenable]インターフェースを増強するインターフェース。

FlutterAPIの中で多くのクラスがこの(Listenable)インターフェースを使用しています。特に以下に示すクラスが関係が深いです。

  • [ChangeNotifier]、[Listenable]インターフェースを実装するオブジェクトを生成するためにChangeNotifierクラスのサブクラスを定義する。
  • [ValueNotifier]、[ValueListenable]を実装したクラスであり、変更された時に通知を発するmutableな値を保持する。

“notify clients”、”send notifications”、”trigger notifications”、”fire notifications”などの用語が同じ意味として使用されます。

abstract class Listenable {

constコンストラクタ。

const Listenable();

オブジェクトがリスナーに(変更を)通知した時に呼び出されるクロージャーを登録する。

void addListener(VoidCallback listener);

オブジェクトが通知するリスナーを、リストから削除する。

void removeListener(VoidCallback listener);

 




ValueListenable<T>

[value]を公開する[Listenable]のサブクラスのインターフェース。

このインターフェースは

[ValueNotifier<T>]

[Animation<T>]

で実装されています。このインターフェースにより、他のAPIはこれらの実装のいずれかを交換可能に受け入れることができます。

abstract class ValueListenable<T> extends Listenable {

  const ValueListenable();

  /// The current value of the object. When the value changes, the callbacks
  /// registered with [addListener] will be invoked.
  ///オブジェクトの現在の値(value)。値(value)が変更された時、[addListener]で登録された
  ///コールバックが呼び出される。
  T get value;
}

 




ChangeNotifier

[VoidCallback]を使って変更通知APIを提供するために継承・実装されるクラス

リスナーを追加する場合はO(1)、リスナーを削除して通知をディスパッチする場合はO(N)です(Nはリスナーの数です)。

class ChangeNotifier implements Listenable {
  LinkedList<_ListenerEntry>? _listeners = LinkedList<_ListenerEntry>();

  bool _debugAssertNotDisposed() {
    assert(() {
      if (_listeners == null) {
        throw FlutterError(
          'A $runtimeType was used after being disposed.\n'
          'Once you have called dispose() on a $runtimeType, it can no longer be used.'
        );
      }
      return true;
    }());
    return true;
  }

  /// Whether any listeners are currently registered.
  ///何らかのリスナーが登録されているかを示す。
  ///
  /// Clients should not depend on this value for their behavior, because having
  /// one listener's logic change when another listener happens to start or stop
  /// listening will lead to extremely hard-to-track bugs. Subclasses might use
  /// this information to determine whether to do any work when there are no
  /// listeners, however; for example, resuming a [Stream] when a listener is
  /// added and pausing it when a listener is removed.
  ///
  /// Typically this is used by overriding [addListener], checking if
  /// [hasListeners] is false before calling `super.addListener()`, and if so,
  /// starting whatever work is needed to determine when to call
  /// [notifyListeners]; and similarly, by overriding [removeListener], checking
  /// if [hasListeners] is false after calling `super.removeListener()`, and if
  /// so, stopping that same work.
  @protected
  bool get hasListeners {
    assert(_debugAssertNotDisposed());
    return _listeners!.isNotEmpty;
  }


オブジェクトが変更されたときに呼び出されるクロージャを登録します。
[dispose]が呼び出された後は、このメソッドを呼び出さないでください。
  @override
  void addListener(VoidCallback listener) {
    assert(_debugAssertNotDisposed());
    _listeners!.add(_ListenerEntry(listener));
  }



  /// オブジェクトが変更されたときに通知されるクロージャのリストから、以前に登録されたクロージャを削除します。
  ///
  /// 指定されたリスナーが登録されていない場合、呼び出しは無視されます。
  ///
  /// [dispose]が呼び出された後は、このメソッドを呼び出さないでください。
  ///
  /// If a listener had been added twice, and is removed once during an
  /// iteration (i.e. in response to a notification), it will still be called
  /// again. If, on the other hand, it is removed as many times as it was
  /// registered, then it will no longer be called. This odd behavior is the
  /// result of the [ChangeNotifier] not being able to determine which listener
  /// is being removed, since they are identical, and therefore conservatively
  /// still calling all the listeners when it knows that any are still
  /// registered.
  ///
  /// This surprising behavior can be unexpectedly observed when registering a
  /// listener on two separate objects which are both forwarding all
  /// registrations to a common upstream object.
  @override
  void removeListener(VoidCallback listener) {
    assert(_debugAssertNotDisposed());
    for (final _ListenerEntry entry in _listeners!) {
      if (entry.listener == listener) {
        entry.unlink();
        return;
      }
    }
  }

///オブジェクトによって使用されているすべてのリソースを破棄します。
///これが呼び出された後、オブジェクトは使用可能な状態ではないため、破棄する必要があります
///([addListener]および[removeListener]の呼び出しは、
///オブジェクトが破棄された後にスローされます)。
///このメソッドは、オブジェクトの所有者のみが呼び出す必要があります。
  @mustCallSuper
  void dispose() {
    assert(_debugAssertNotDisposed());
    _listeners = null;
  }





  /// 登録されているすべてのリスナーを呼び出します。
  ///
  /// オブジェクトが変更されるたびにこのメソッドを呼び出して、オブジェクトが変更された可能性があることをクライアントに通知します。
この反復中に追加されたリスナーは訪問されません。
この反復中に削除されたリスナーは、削除された後はアクセスされません。
  ///
  /// リスナーによってスローされた例外は、[FlutterError.reportError]を使用してキャッチおよび報告されます。
  ///
  /// [dispose]が呼び出された後は、このメソッドを呼び出さないでください。
  ///
  /// Surprising behavior can result when reentrantly removing a listener (i.e.
  /// in response to a notification) that has been registered multiple times.
  /// See the discussion at [removeListener].
  @protected
  @visibleForTesting
  void notifyListeners() {
    assert(_debugAssertNotDisposed());
    if (_listeners!.isEmpty)
      return;

    final List<_ListenerEntry> localListeners = List<_ListenerEntry>.from(_listeners!);

    for (final _ListenerEntry entry in localListeners) {
      try {
        if (entry.list != null)
          entry.listener();
      } catch (exception, stack) {
        FlutterError.reportError(FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'foundation library',
          context: ErrorDescription('while dispatching notifications for $runtimeType'),
          informationCollector: () sync* {
            yield DiagnosticsProperty<ChangeNotifier>(
              'The $runtimeType sending notification was',
              this,
              style: DiagnosticsTreeStyle.errorProperty,
            );
          },
        ));
      }
    }
  }
}

ChangeNotifierが持っているのは

_listenersフィールド :  リスナー(コールバック)のリスト

bool hasListenersゲッター :  リスナーのがあるかどうか。

void disposeメソッド :  リソースの開放。

addListenerメソッド : リスナー追加。

removeListenerメソッド : リスナー削除。

void notifyListenersメソッド : リスナーを実行する。




ValueNotifier<T>

class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
  /// Creates a [ChangeNotifier] that wraps this value.
  ValueNotifier(this._value);

  /// The current value stored in this notifier.
  ///
  /// When the value is replaced with something that is not equal to the old
  /// value as evaluated by the equality operator ==, this class notifies its
  /// listeners.
  @override
  T get value => _value;
  T _value;
  set value(T newValue) {
    if (_value == newValue)
      return;
    _value = newValue;
    notifyListeners();
  }

  @override
  String toString() => '${describeIdentity(this)}($value)';
}

ValueNotifier<T>はChangeNotifierのサブクラスなので

_valueフィールド(T型)を持つChangeNotifierという感じ。

サブクラスなので当然ChangeNotifierのメンバは全て持っている。

_valueフィールドが変更されると、notifyListeners()メソッドを実行する

=>リスナーを全て実行する。

 

参考

https://github.com/flutter/flutter/blob/7891006299/packages/flutter/lib/src/foundation/change_notifier.dart#L55





ChangeNotifierProvider<T extends ChangeNotifier> class

ChangeNotifierをリッスンして、子孫ウィジェットに公開し、ChangeNotifier.notifyListenersが呼び出される度に、依存しているウィジェットをリビルドします。

ChangeNotifierを新たに生成するか、再利用するかによって、使用するコンストラクタが異なります。


Creating a ChangeNotifier:

値を新しく生成するにはデフォルトコンストラクタを使用します。ChangeNotifier.valueを使用してbuild内部にインスタンスを生成すると、メモリリークが発生し、望ましくない副作用が発生する可能性があります。

createパラメータのコールバックの中でChangeNotifierを生成してください。

ChangeNotifierProvider(
  create: (_) => new MyChangeNotifier(),
  child: ...
)

  • DON’T use ChangeNotifierProvider.value to create your ChangeNotifier.

ChangeNotifierを生成するのにChangeNotifierProvider.valueコンストラクタを使用しないでください。

ChangeNotifierProvider.value(
  value: new MyChangeNotifier(),
  child: ...
)

Reusing an existing instance of ChangeNotifier:

ChangeNotifier型のインスタンスを再利用する:

既存のChangeNotifierインスタンスがあり、それを子孫ウィジェットに公開したい場合、デフォルトコンストラクタの代わりにChangeNotifierProvider.valueコンストラクタを使用してください。

そうしないと、ChangeNotifierがまだ使用されているときに破棄される可能性があります。

既存のChangeNotifierインスタンスを提供するために、ChangeNotifierProvider.valueコンストラクタを使用してください。

MyChangeNotifier variable;

ChangeNotifierProvider.value(
  value: variable,
  child: ...
)

  • DON’T reuse an existing ChangeNotifier using the default constructor

既存のChangeNotifierインスタンスを再利用するためにデフォルトコンストラクタを使用しないでください

MyChangeNotifier variable;

ChangeNotifierProvider(
  create: (_) => variable,
  child: ...
)

多分notifyListenersを呼び出すかどうかを

 


抽象クラスのInheritedWidgetが、updateShouldNotifyメソッドを宣言している。

InheritedWidgetのサブクラスが、updateShouldNotifyメソッドを実装する。

(ChangeNotifierProviderの先祖クラスである)InheritedProviderクラスのbuildメソッドで返される_InheritedProviderScope<T>クラスでupdateShouldNotifyメソッドが

@override
bool updateShouldNotify(InheritedWidget oldWidget) {
  return false;
}

のようにオーバーライドされている。これがデフォルト、ということか。

updateShouldNotifyが返した値でnotifyListenersを呼び出すか決める、ということか。

abstract class InheritedWidget extends ProxyWidget {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

  @override
  InheritedElement createElement() => InheritedElement(this);

  /// Whether the framework should notify widgets that inherit from this widget.
  ///
  /// When this widget is rebuilt, sometimes we need to rebuild the widgets that
  /// inherit from this widget but sometimes we do not. For example, if the data
  /// held by this widget is the same as the data held by `oldWidget`, then we
  /// do not need to rebuild the widgets that inherited the data held by
  /// `oldWidget`.
  ///
  /// The framework distinguishes these cases by calling this function with the
  /// widget that previously occupied this location in the tree as an argument.
  /// The given widget is guaranteed to have the same [runtimeType] as this
  /// object.
  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

 

 

 

参考

https://pub.dev/documentation/provider/latest/provider/ChangeNotifierProvider-class.html

コメントを残す

メールアドレスが公開されることはありません。