Flutternの内部構造
![JboyHashimoto](https://res.cloudinary.com/zenn/image/fetch/s--CHq5qY5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/3c90bcc43f.jpeg)
StatelessWidget
official
実装するときに、build()
メソッドに表示したいWidgetを配置するば、このメソッドが呼ばれることで画面が描画されることがわかる。いつこのメソッドが呼ばれるのか?
内部実装を見てみる
abstract class StatelessWidget extends Widget {
/// Initializes [key] for subclasses.
const StatelessWidget({ super.key });
/// Creates a [StatelessElement] to manage this widget's location in the tree.
///
/// It is uncommon for subclasses to override this method.
StatelessElement createElement() => StatelessElement(this);
/// Describes the part of the user interface represented by this widget.
///
/// The framework calls this method when this widget is inserted into the tree
/// in a given [BuildContext] and when the dependencies of this widget change
/// (e.g., an [InheritedWidget] referenced by this widget changes). This
/// method can potentially be called in every frame and should not have any side
/// effects beyond building a widget.
///
/// The framework replaces the subtree below this widget with the widget
/// returned by this method, either by updating the existing subtree or by
/// removing the subtree and inflating a new subtree, depending on whether the
/// widget returned by this method can update the root of the existing
/// subtree, as determined by calling [Widget.canUpdate].
///
/// Typically implementations return a newly created constellation of widgets
/// that are configured with information from this widget's constructor and
/// from the given [BuildContext].
///
/// The given [BuildContext] contains information about the location in the
/// tree at which this widget is being built. For example, the context
/// provides the set of inherited widgets for this location in the tree. A
/// given widget might be built with multiple different [BuildContext]
/// arguments over time if the widget is moved around the tree or if the
/// widget is inserted into the tree in multiple places at once.
///
/// The implementation of this method must only depend on:
///
/// * the fields of the widget, which themselves must not change over time,
/// and
/// * any ambient state obtained from the `context` using
/// [BuildContext.dependOnInheritedWidgetOfExactType].
///
/// If a widget's [build] method is to depend on anything else, use a
/// [StatefulWidget] instead.
///
/// See also:
///
/// * [StatelessWidget], which contains the discussion on performance considerations.
Widget build(BuildContext context);
}
![JboyHashimoto](https://res.cloudinary.com/zenn/image/fetch/s--CHq5qY5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/3c90bcc43f.jpeg)
本にも書いてあったが読んでもわからない。スーパークラスのWidgetクラスが多くのことをやってくれているのか。Widgetクラスの内部実装を見てみる。
abstract class Widget extends DiagnosticableTree {
/// Initializes [key] for subclasses.
const Widget({ this.key });
/// Controls how one widget replaces another widget in the tree.
///
/// If the [runtimeType] and [key] properties of the two widgets are
/// [operator==], respectively, then the new widget replaces the old widget by
/// updating the underlying element (i.e., by calling [Element.update] with the
/// new widget). Otherwise, the old element is removed from the tree, the new
/// widget is inflated into an element, and the new element is inserted into the
/// tree.
///
/// In addition, using a [GlobalKey] as the widget's [key] allows the element
/// to be moved around the tree (changing parent) without losing state. When a
/// new widget is found (its key and type do not match a previous widget in
/// the same location), but there was a widget with that same global key
/// elsewhere in the tree in the previous frame, then that widget's element is
/// moved to the new location.
///
/// Generally, a widget that is the only child of another widget does not need
/// an explicit key.
///
/// See also:
///
/// * The discussions at [Key] and [GlobalKey].
final Key? key;
/// Inflates this configuration to a concrete instance.
///
/// A given widget can be included in the tree zero or more times. In particular
/// a given widget can be placed in the tree multiple times. Each time a widget
/// is placed in the tree, it is inflated into an [Element], which means a
/// widget that is incorporated into the tree multiple times will be inflated
/// multiple times.
Element createElement();
/// A short, textual description of this widget.
String toStringShort() {
final String type = objectRuntimeType(this, 'Widget');
return key == null ? type : '$type-$key';
}
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
}
bool operator ==(Object other) => super == other;
int get hashCode => super.hashCode;
/// Whether the `newWidget` can be used to update an [Element] that currently
/// has the `oldWidget` as its configuration.
///
/// An element that uses a given widget as its configuration can be updated to
/// use another widget as its configuration if, and only if, the two widgets
/// have [runtimeType] and [key] properties that are [operator==].
///
/// If the widgets have no key (their key is null), then they are considered a
/// match if they have the same type, even if their children are completely
/// different.
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
// Return a numeric encoding of the specific `Widget` concrete subtype.
// This is used in `Element.updateChild` to determine if a hot reload modified the
// superclass of a mounted element's configuration. The encoding of each `Widget`
// must match the corresponding `Element` encoding in `Element._debugConcreteSubtype`.
static int _debugConcreteSubtype(Widget widget) {
return widget is StatefulWidget ? 1 :
widget is StatelessWidget ? 2 :
0;
}
}
こちらも特別なことをしてないらしい。DiagnosticableTreeをたどっても特別なことはしていないらしい????
abstract class DiagnosticableTree with Diagnosticable {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const DiagnosticableTree();
/// Returns a one-line detailed description of the object.
///
/// This description is often somewhat long. This includes the same
/// information given by [toStringDeep], but does not recurse to any children.
///
/// `joiner` specifies the string which is place between each part obtained
/// from [debugFillProperties]. Passing a string such as `'\n '` will result
/// in a multiline string that indents the properties of the object below its
/// name (as per [toString]).
///
/// `minLevel` specifies the minimum [DiagnosticLevel] for properties included
/// in the output.
///
/// See also:
///
/// * [toString], for a brief description of the object.
/// * [toStringDeep], for a description of the subtree rooted at this object.
String toStringShallow({
String joiner = ', ',
DiagnosticLevel minLevel = DiagnosticLevel.debug,
}) {
String? shallowString;
assert(() {
final StringBuffer result = StringBuffer();
result.write(toString());
result.write(joiner);
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
debugFillProperties(builder);
result.write(
builder.properties.where((DiagnosticsNode n) => !n.isFiltered(minLevel))
.join(joiner),
);
shallowString = result.toString();
return true;
}());
return shallowString ?? toString();
}
/// Returns a string representation of this node and its descendants.
///
/// `prefixLineOne` will be added to the front of the first line of the
/// output. `prefixOtherLines` will be added to the front of each other line.
/// If `prefixOtherLines` is null, the `prefixLineOne` is used for every line.
/// By default, there is no prefix.
///
/// `minLevel` specifies the minimum [DiagnosticLevel] for properties included
/// in the output.
///
/// The [toStringDeep] method takes other arguments, but those are intended
/// for internal use when recursing to the descendants, and so can be ignored.
///
/// See also:
///
/// * [toString], for a brief description of the object but not its children.
/// * [toStringShallow], for a detailed description of the object but not its
/// children.
String toStringDeep({
String prefixLineOne = '',
String? prefixOtherLines,
DiagnosticLevel minLevel = DiagnosticLevel.debug,
}) {
return toDiagnosticsNode().toStringDeep(prefixLineOne: prefixLineOne, prefixOtherLines: prefixOtherLines, minLevel: minLevel);
}
String toStringShort() => describeIdentity(this);
DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {
return DiagnosticableTreeNode(
name: name,
value: this,
style: style,
);
}
/// Returns a list of [DiagnosticsNode] objects describing this node's
/// children.
///
/// Children that are offstage should be added with `style` set to
/// [DiagnosticsTreeStyle.offstage] to indicate that they are offstage.
///
/// The list must not contain any null entries. If there are explicit null
/// children to report, consider [DiagnosticsNode.message] or
/// [DiagnosticsProperty<Object>] as possible [DiagnosticsNode] objects to
/// provide.
///
/// Used by [toStringDeep], [toDiagnosticsNode] and [toStringShallow].
///
/// See also:
///
/// * [RenderTable.debugDescribeChildren], which provides high quality custom
/// descriptions for its child nodes.
List<DiagnosticsNode> debugDescribeChildren() => const <DiagnosticsNode>[];
}
createElement()
メソッドでインスタンスしているStatelessElementクラスが重要だそうだ。
Element createElement();
/// Creates a [StatelessElement] to manage this widget's location in the tree.
///
/// It is uncommon for subclasses to override this method.
StatelessElement createElement() => StatelessElement(this);
Widget classは、createElement()
メソッドでエレメントクラスを必ずインスタンスしている。
開発者は、Widget class をtree構造に記述していくのでWidget Tree構造というのはわかると思う。エレメントクラスもなったく同じツリー構造になっている。Widgetがインスタンスされた後、Flutter Widgetに、エレメントを要求し、そのエレメントは、自信を作成したWidgetの参照を持ち、エレメントTreeに追加される。
昔の本が、nullのままだったので、null safetyに対応
class LoadingWidget extends StatelessWidget {
final bool isLoading;
const LoadingWidget({super.key, required this.isLoading});
Widget build(BuildContext context) {
return isLoading
? const DecoratedBox(
decoration: BoxDecoration(
color: Color(0x44000000),
),
child: Center(
child: CircularProgressIndicator(),
),
)
: const SizedBox.shrink();
}
}
LoadingWidget クラスがインスタンスされると対応したStatelessElementクラスもインスタンスされる。StatelessElementクラスは、LoadingWidgetオブジェクトの参照を保持する。
![JboyHashimoto](https://res.cloudinary.com/zenn/image/fetch/s--CHq5qY5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/3c90bcc43f.jpeg)
StatelessWidgetクラスのライフサイクルでは、mounted()
メソッドが呼ばれる前をinitial状態
としているらしい。mounted()
メソッドが呼ばれたあとをactive状態
としている。
StatelessWidgetクラスは、imutable(不変)。つまりWidgetが構築されたときに、1回しか描画されない。イベントやユーザーアクションに基付いて再描画することはない。
変更されないのでライフサイクルは2つだけ
- 描画される前の
initial状態
- 描画後の
active状態
ホットリロード時は、reassemble()
メソッドが呼ばれ、そこでルートのエレメントの再構築フラグを立てて、その場合に再構築が走るようになる。
![JboyHashimoto](https://res.cloudinary.com/zenn/image/fetch/s--CHq5qY5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/3c90bcc43f.jpeg)
StatefulWidget
状態をもつStateオブジェクトがあるので、StatelessWidgetクラスほど単縦ではない。Stateクラスと呼んでた💦
import 'package:flutter/material.dart';
class StatefulExample extends StatefulWidget {
const StatefulExample({super.key});
State<StatefulExample> createState() => _StatefulExampleState();
}
class _StatefulExampleState extends State<StatefulExample> {
Widget build(BuildContext context) {
return const Placeholder();
}
}
StatefulWidgetkクラスにもエレメントがある。StatefuleElementクラス。createElement()
メソッドでインスタンスする。StatefulElement
クラスのコンストラクタを見るとStatefulElementクラスは、Stateオブジェクトを保持している。
/// An [Element] that uses a [StatefulWidget] as its configuration.
class StatefulElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatefulElement(StatefulWidget widget)
: _state = widget.createState(),
super(widget) {
assert(() {
if (!state._debugTypesAreRight(widget)) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('StatefulWidget.createState must return a subtype of State<${widget.runtimeType}>'),
ErrorDescription(
'The createState function for ${widget.runtimeType} returned a state '
'of type ${state.runtimeType}, which is not a subtype of '
'State<${widget.runtimeType}>, violating the contract for createState.',
),
]);
}
return true;
}());
assert(state._element == null);
state._element = this;
assert(
state._widget == null,
'The createState function for $widget returned an old or invalid state '
'instance: ${state._widget}, which is not null, violating the contract '
'for createState.',
);
state._widget = widget;
assert(state._debugLifecycleState == _StateLifecycle.created);
}
Widget build() => state.build(this);
/// The [State] instance associated with this location in the tree.
///
/// There is a one-to-one relationship between [State] objects and the
/// [StatefulElement] objects that hold them. The [State] objects are created
/// by [StatefulElement] in [mount].
State<StatefulWidget> get state => _state!;
State<StatefulWidget>? _state;
void reassemble() {
state.reassemble();
super.reassemble();
}
void _firstBuild() {
assert(state._debugLifecycleState == _StateLifecycle.created);
final Object? debugCheckForReturnedFuture = state.initState() as dynamic;
assert(() {
if (debugCheckForReturnedFuture is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('${state.runtimeType}.initState() returned a Future.'),
ErrorDescription('State.initState() must be a void method without an `async` keyword.'),
ErrorHint(
'Rather than awaiting on asynchronous work directly inside of initState, '
'call a separate method to do this work without awaiting it.',
),
]);
}
return true;
}());
assert(() {
state._debugLifecycleState = _StateLifecycle.initialized;
return true;
}());
state.didChangeDependencies();
assert(() {
state._debugLifecycleState = _StateLifecycle.ready;
return true;
}());
super._firstBuild();
}
void performRebuild() {
if (_didChangeDependencies) {
state.didChangeDependencies();
_didChangeDependencies = false;
}
super.performRebuild();
}
void update(StatefulWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
final StatefulWidget oldWidget = state._widget!;
state._widget = widget as StatefulWidget;
final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
assert(() {
if (debugCheckForReturnedFuture is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('${state.runtimeType}.didUpdateWidget() returned a Future.'),
ErrorDescription( 'State.didUpdateWidget() must be a void method without an `async` keyword.'),
ErrorHint(
'Rather than awaiting on asynchronous work directly inside of didUpdateWidget, '
'call a separate method to do this work without awaiting it.',
),
]);
}
return true;
}());
rebuild(force: true);
}
void activate() {
super.activate();
state.activate();
// Since the State could have observed the deactivate() and thus disposed of
// resources allocated in the build method, we have to rebuild the widget
// so that its State can reallocate its resources.
assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
markNeedsBuild();
}
void deactivate() {
state.deactivate();
super.deactivate();
}
void unmount() {
super.unmount();
state.dispose();
assert(() {
if (state._debugLifecycleState == _StateLifecycle.defunct) {
return true;
}
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('${state.runtimeType}.dispose failed to call super.dispose.'),
ErrorDescription(
'dispose() implementations must always call their superclass dispose() method, to ensure '
'that all the resources used by the widget are fully released.',
),
]);
}());
state._element = null;
// Release resources to reduce the severity of memory leaks caused by
// defunct, but accidentally retained Elements.
_state = null;
}
InheritedWidget dependOnInheritedElement(Element ancestor, { Object? aspect }) {
assert(() {
final Type targetType = ancestor.widget.runtimeType;
if (state._debugLifecycleState == _StateLifecycle.created) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('dependOnInheritedWidgetOfExactType<$targetType>() or dependOnInheritedElement() was called before ${state.runtimeType}.initState() completed.'),
ErrorDescription(
'When an inherited widget changes, for example if the value of Theme.of() changes, '
"its dependent widgets are rebuilt. If the dependent widget's reference to "
'the inherited widget is in a constructor or an initState() method, '
'then the rebuilt dependent widget will not reflect the changes in the '
'inherited widget.',
),
ErrorHint(
'Typically references to inherited widgets should occur in widget build() methods. Alternatively, '
'initialization based on inherited widgets can be placed in the didChangeDependencies method, which '
'is called after initState and whenever the dependencies change thereafter.',
),
]);
}
if (state._debugLifecycleState == _StateLifecycle.defunct) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('dependOnInheritedWidgetOfExactType<$targetType>() or dependOnInheritedElement() was called after dispose(): $this'),
ErrorDescription(
'This error happens if you call dependOnInheritedWidgetOfExactType() on the '
'BuildContext for a widget that no longer appears in the widget tree '
'(e.g., whose parent widget no longer includes the widget in its '
'build). This error can occur when code calls '
'dependOnInheritedWidgetOfExactType() from a timer or an animation callback.',
),
ErrorHint(
'The preferred solution is to cancel the timer or stop listening to the '
'animation in the dispose() callback. Another solution is to check the '
'"mounted" property of this object before calling '
'dependOnInheritedWidgetOfExactType() to ensure the object is still in the '
'tree.',
),
ErrorHint(
'This error might indicate a memory leak if '
'dependOnInheritedWidgetOfExactType() is being called because another object '
'is retaining a reference to this State object after it has been '
'removed from the tree. To avoid memory leaks, consider breaking the '
'reference to this object during dispose().',
),
]);
}
return true;
}());
return super.dependOnInheritedElement(ancestor as InheritedElement, aspect: aspect);
}
/// This controls whether we should call [State.didChangeDependencies] from
/// the start of [build], to avoid calls when the [State] will not get built.
/// This can happen when the widget has dropped out of the tree, but depends
/// on an [InheritedWidget] that is still in the tree.
///
/// It is set initially to false, since [_firstBuild] makes the initial call
/// on the [state]. When it is true, [build] will call
/// `state.didChangeDependencies` and then sets it to false. Subsequent calls
/// to [didChangeDependencies] set it to true.
bool _didChangeDependencies = false;
void didChangeDependencies() {
super.didChangeDependencies();
_didChangeDependencies = true;
}
DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {
return _ElementDiagnosticableTreeNode(
name: name,
value: this,
style: style,
stateful: true,
);
}
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<State<StatefulWidget>>('state', _state, defaultValue: null));
}
}
![JboyHashimoto](https://res.cloudinary.com/zenn/image/fetch/s--CHq5qY5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/3c90bcc43f.jpeg)
つまり状態管理をしているのは、エレメントクラスになる。知らなかった💦
StatefulWidgetクラスもStatelessWidgetクラスと同様に変更可能な状態を保持していないらしい。
例えば、プロジェクト作成時にあるMyHomePageクラスがインスタンスされると対応したStatefulElementクラスもインスタンスされる。_MyHomePageStateクラスもインスタンスされる。StatefulElementクラスは、WidgetオブジェクトとStateオブジェクトの両方の参照を保持している。
これは、HomePageではないけど、仕組みは同じ。StatefulExampleクラスがインスタンスされると、StatefuleElementクラスもインスタンスされる。_StatefulExampleStateクラスもインスタンスされる。2つ保持しているから、重いのだろうな....
同じインスタンスを生成しないシングルトンを使うと良いというのは、複数のインスタンスが生成されるとメモリの使用量が増えるからだろうな。
探求すると複雑な話になるみたい💦
import 'package:flutter/material.dart';
class StatefulExample extends StatefulWidget {
const StatefulExample({super.key});
State<StatefulExample> createState() => _StatefulExampleState();
}
class _StatefulExampleState extends State<StatefulExample> {
int _counter = 0;
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
_counter++;
});
},
child: Text('Counter: $_counter'),
);
}
}
![JboyHashimoto](https://res.cloudinary.com/zenn/image/fetch/s--CHq5qY5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/3c90bcc43f.jpeg)
Stateクラスには、ライフサイクルに関係した列挙型(enum)がある。
enum _StateLifecycle {
/// The [State] object has been created. [State.initState] is called at this
/// time.
created,
/// The [State.initState] method has been called but the [State] object is
/// not yet ready to build. [State.didChangeDependencies] is called at this time.
initialized,
/// The [State] object is ready to build and [State.dispose] has not yet been
/// called.
ready,
/// The [State.dispose] method has been called and the [State] object is
/// no longer able to build.
defunct,
}
これは、Stateクラスでオーバーライド可能なinitState()メソッド、didChangeDependencies()メソッド、dispose()メソッドの呼び出しに対応している。
ライフサイクルは、created(MyHomaPageState) -> initialized(initStateが終わった) -> ready(didChangeDependenciesが終わった) defunct(disposeが終わった) -> ○
initialized状態への変化とready状態への変化は、mount()
メソッド内、_firstBuild()
メソッドで行われないている。StatefulElementクラスは、このメソッドをオーバーライドしていて、StatelessElementクラスの_firstBuild()
メソッドとは違う処理になっているらしい。
abstract class ComponentElement extends Element {
/// Creates an element that uses the given widget as its configuration.
ComponentElement(super.widget);
Element? _child;
bool _debugDoingBuild = false;
bool get debugDoingBuild => _debugDoingBuild;
Element? get renderObjectAttachingChild => _child;
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_lifecycleState == _ElementLifecycle.active);
_firstBuild();
assert(_child != null);
}
void _firstBuild() {
// StatefulElement overrides this to also call state.didChangeDependencies.
rebuild(); // This eventually calls performRebuild.
}
/// Calls the [StatelessWidget.build] method of the [StatelessWidget] object
/// (for stateless widgets) or the [State.build] method of the [State] object
/// (for stateful widgets) and then updates the widget tree.
///
/// Called automatically during [mount] to generate the first build, and by
/// [rebuild] when the element needs updating.
('vm:notify-debugger-on-exception')
void performRebuild() {
Widget? built;
try {
assert(() {
_debugDoingBuild = true;
return true;
}());
built = build();
assert(() {
_debugDoingBuild = false;
return true;
}());
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
_debugDoingBuild = false;
built = ErrorWidget.builder(
_reportException(
ErrorDescription('building $this'),
e,
stack,
informationCollector: () => <DiagnosticsNode>[
if (kDebugMode)
DiagnosticsDebugCreator(DebugCreator(this)),
],
),
);
} finally {
// We delay marking the element as clean until after calling build() so
// that attempts to markNeedsBuild() during build() will be ignored.
super.performRebuild(); // clears the "dirty" flag
}
try {
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
built = ErrorWidget.builder(
_reportException(
ErrorDescription('building $this'),
e,
stack,
informationCollector: () => <DiagnosticsNode>[
if (kDebugMode)
DiagnosticsDebugCreator(DebugCreator(this)),
],
),
);
_child = updateChild(null, built, slot);
}
}
/// Subclasses should override this function to actually call the appropriate
/// `build` function (e.g., [StatelessWidget.build] or [State.build]) for
/// their widget.
Widget build();
void visitChildren(ElementVisitor visitor) {
if (_child != null) {
visitor(_child!);
}
}
void forgetChild(Element child) {
assert(child == _child);
_child = null;
super.forgetChild(child);
}
}
/// An [Element] that uses a [StatelessWidget] as its configuration.
class StatelessElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatelessElement(StatelessWidget super.widget);
Widget build() => (widget as StatelessWidget).build(this);
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
rebuild(force: true);
}
}
/// An [Element] that uses a [StatefulWidget] as its configuration.
class StatefulElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatefulElement(StatefulWidget widget)
: _state = widget.createState(),
super(widget) {
assert(() {
if (!state._debugTypesAreRight(widget)) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('StatefulWidget.createState must return a subtype of State<${widget.runtimeType}>'),
ErrorDescription(
'The createState function for ${widget.runtimeType} returned a state '
'of type ${state.runtimeType}, which is not a subtype of '
'State<${widget.runtimeType}>, violating the contract for createState.',
),
]);
}
return true;
}());
assert(state._element == null);
state._element = this;
assert(
state._widget == null,
'The createState function for $widget returned an old or invalid state '
'instance: ${state._widget}, which is not null, violating the contract '
'for createState.',
);
state._widget = widget;
assert(state._debugLifecycleState == _StateLifecycle.created);
}
Widget build() => state.build(this);
/// The [State] instance associated with this location in the tree.
///
/// There is a one-to-one relationship between [State] objects and the
/// [StatefulElement] objects that hold them. The [State] objects are created
/// by [StatefulElement] in [mount].
State<StatefulWidget> get state => _state!;
State<StatefulWidget>? _state;
void reassemble() {
state.reassemble();
super.reassemble();
}
void _firstBuild() {
assert(state._debugLifecycleState == _StateLifecycle.created);
final Object? debugCheckForReturnedFuture = state.initState() as dynamic;
assert(() {
if (debugCheckForReturnedFuture is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('${state.runtimeType}.initState() returned a Future.'),
ErrorDescription('State.initState() must be a void method without an `async` keyword.'),
ErrorHint(
'Rather than awaiting on asynchronous work directly inside of initState, '
'call a separate method to do this work without awaiting it.',
),
]);
}
return true;
}());
assert(() {
state._debugLifecycleState = _StateLifecycle.initialized;
return true;
}());
state.didChangeDependencies();
assert(() {
state._debugLifecycleState = _StateLifecycle.ready;
return true;
}());
super._firstBuild();
}
void performRebuild() {
if (_didChangeDependencies) {
state.didChangeDependencies();
_didChangeDependencies = false;
}
super.performRebuild();
}
void update(StatefulWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
final StatefulWidget oldWidget = state._widget!;
state._widget = widget as StatefulWidget;
final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
assert(() {
if (debugCheckForReturnedFuture is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('${state.runtimeType}.didUpdateWidget() returned a Future.'),
ErrorDescription( 'State.didUpdateWidget() must be a void method without an `async` keyword.'),
ErrorHint(
'Rather than awaiting on asynchronous work directly inside of didUpdateWidget, '
'call a separate method to do this work without awaiting it.',
),
]);
}
return true;
}());
rebuild(force: true);
}
void activate() {
super.activate();
state.activate();
// Since the State could have observed the deactivate() and thus disposed of
// resources allocated in the build method, we have to rebuild the widget
// so that its State can reallocate its resources.
assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
markNeedsBuild();
}
void deactivate() {
state.deactivate();
super.deactivate();
}
void unmount() {
super.unmount();
state.dispose();
assert(() {
if (state._debugLifecycleState == _StateLifecycle.defunct) {
return true;
}
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('${state.runtimeType}.dispose failed to call super.dispose.'),
ErrorDescription(
'dispose() implementations must always call their superclass dispose() method, to ensure '
'that all the resources used by the widget are fully released.',
),
]);
}());
state._element = null;
// Release resources to reduce the severity of memory leaks caused by
// defunct, but accidentally retained Elements.
_state = null;
}
InheritedWidget dependOnInheritedElement(Element ancestor, { Object? aspect }) {
assert(() {
final Type targetType = ancestor.widget.runtimeType;
if (state._debugLifecycleState == _StateLifecycle.created) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('dependOnInheritedWidgetOfExactType<$targetType>() or dependOnInheritedElement() was called before ${state.runtimeType}.initState() completed.'),
ErrorDescription(
'When an inherited widget changes, for example if the value of Theme.of() changes, '
"its dependent widgets are rebuilt. If the dependent widget's reference to "
'the inherited widget is in a constructor or an initState() method, '
'then the rebuilt dependent widget will not reflect the changes in the '
'inherited widget.',
),
ErrorHint(
'Typically references to inherited widgets should occur in widget build() methods. Alternatively, '
'initialization based on inherited widgets can be placed in the didChangeDependencies method, which '
'is called after initState and whenever the dependencies change thereafter.',
),
]);
}
if (state._debugLifecycleState == _StateLifecycle.defunct) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('dependOnInheritedWidgetOfExactType<$targetType>() or dependOnInheritedElement() was called after dispose(): $this'),
ErrorDescription(
'This error happens if you call dependOnInheritedWidgetOfExactType() on the '
'BuildContext for a widget that no longer appears in the widget tree '
'(e.g., whose parent widget no longer includes the widget in its '
'build). This error can occur when code calls '
'dependOnInheritedWidgetOfExactType() from a timer or an animation callback.',
),
ErrorHint(
'The preferred solution is to cancel the timer or stop listening to the '
'animation in the dispose() callback. Another solution is to check the '
'"mounted" property of this object before calling '
'dependOnInheritedWidgetOfExactType() to ensure the object is still in the '
'tree.',
),
ErrorHint(
'This error might indicate a memory leak if '
'dependOnInheritedWidgetOfExactType() is being called because another object '
'is retaining a reference to this State object after it has been '
'removed from the tree. To avoid memory leaks, consider breaking the '
'reference to this object during dispose().',
),
]);
}
return true;
}());
return super.dependOnInheritedElement(ancestor as InheritedElement, aspect: aspect);
}
/// This controls whether we should call [State.didChangeDependencies] from
/// the start of [build], to avoid calls when the [State] will not get built.
/// This can happen when the widget has dropped out of the tree, but depends
/// on an [InheritedWidget] that is still in the tree.
///
/// It is set initially to false, since [_firstBuild] makes the initial call
/// on the [state]. When it is true, [build] will call
/// `state.didChangeDependencies` and then sets it to false. Subsequent calls
/// to [didChangeDependencies] set it to true.
bool _didChangeDependencies = false;
void didChangeDependencies() {
super.didChangeDependencies();
_didChangeDependencies = true;
}
DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {
return _ElementDiagnosticableTreeNode(
name: name,
value: this,
style: style,
stateful: true,
);
}
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<State<StatefulWidget>>('state', _state, defaultValue: null));
}
}
![JboyHashimoto](https://res.cloudinary.com/zenn/image/fetch/s--CHq5qY5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/3c90bcc43f.jpeg)
StateクラスのinitState()メソッドやdidChangeDependencies()メソッドが呼ばれている。長くて探すの大変💦
Stateクラスで@override(オーバーライド)可能なメソッドについて見ていく。
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
void initState() {
print('ページが呼ばれたら実行');
super.initState();
}
void didChangeDependencies() {
print('ページが呼ばれたら実行');
super.didChangeDependencies();
}
void dispose() {
print('ページが閉じられたら実行');
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
![JboyHashimoto](https://res.cloudinary.com/zenn/image/fetch/s--CHq5qY5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/3c90bcc43f.jpeg)
initState()メソッドは、StatefulElementクラスのmount()メソッド内で呼ばれる。エレメントツリーへ追加した後に呼ばれる。名かしら初期をしたい場合にオーバーライドするメソッド。
- animaitionの初期化
- controllerの初期化
didChangeDepenceies()メソッドは、initState内では、利用できなかったcontext
を使った処理を使いたいためにオーバーライドする。dependOnInheritedWidgetOfExactType()メソッドを呼び出すことで、InheritedWidget classを取得可能。
![JboyHashimoto](https://res.cloudinary.com/zenn/image/fetch/s--CHq5qY5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/3c90bcc43f.jpeg)
build()メソッドは、オーバーライド必須のメソッド。描画したいWidgetを記述するのに使う。このメソッドは、Stateオブジェクトが変更されるたびに呼ばれる。つまりsetState()
メソッドを呼び出すことで強制的に構築される。
![JboyHashimoto](https://res.cloudinary.com/zenn/image/fetch/s--CHq5qY5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/3c90bcc43f.jpeg)
didUpdateWidget()メソッド。Stateクラスの場合でもStateクラスが参照しているStatefulWidgetのウィジェットツリー上の親ウィジェットが再構築されて、その際にコンストラクタで渡ってくる値が違う場合は、ウィジェットを再構築する必要がある。
didUpdateWidget()メソッドはそういうときに使う。その場合でもFlutterは、Stateクラス自体を再利用するため、didUpdateWidget()メソッド内で、initState()メソッドで行っていた処理を解除し、再度、行うようにすると良いそう。
FWは常に、didUpdateWidget()メソッドを呼び出した後に、build()メソッドを呼び出す。つまりdidUpdateWidget()メソッド内で、setState()メソッドを呼び出す必要はない。
![JboyHashimoto](https://res.cloudinary.com/zenn/image/fetch/s--CHq5qY5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/3c90bcc43f.jpeg)
dispose()メソッドは、ウィジェットが破棄されたときに呼ばれる。アニメーションコントローラーやコントラーローラー、Streamなどの後始末をこのメソッドでやる必要がある。
![JboyHashimoto](https://res.cloudinary.com/zenn/image/fetch/s--CHq5qY5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/3c90bcc43f.jpeg)
StatefulWidgetクラスは、Stateクラス内の値を変化させることで再描画できる。変化させたい場合に呼び出すのが、setState()メソッド。Stateクラス内の値を変更したい場合は、必ずsetState()が呼ばれている。
setState()メソッドを呼び出したいときに、新しいWidgetが生成され、古い参照を新しいWidgetに変えるだけというのは、パフォーマンス的に良いとのこと。これはエレメントツリーもStateクラスへの参照も維持されることを意味する。
さらにもう一度、生成されたエレメントツリーやStateクラスは、StatefuleWidgetクラスの上位にあるWidgetによるリビルドが走っても維持されるとのこと。