From aef338d69ef39403d0b0f7d65fed2d61027137f5 Mon Sep 17 00:00:00 2001 From: Nate Wilson Date: Mon, 17 Mar 2025 17:28:07 -0600 Subject: [PATCH 1/5] update documentation --- packages/flutter_hooks/lib/src/framework.dart | 82 +++++++++++-------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/packages/flutter_hooks/lib/src/framework.dart b/packages/flutter_hooks/lib/src/framework.dart index 1ba423a..3939e80 100644 --- a/packages/flutter_hooks/lib/src/framework.dart +++ b/packages/flutter_hooks/lib/src/framework.dart @@ -10,20 +10,27 @@ bool debugHotReloadHooksEnabled = true; /// Registers a [Hook] and returns its value. /// -/// [use] must be called within the `build` method of either [HookWidget] or [StatefulHookWidget]. +/// This function must be called inside the `build` method of a widget +/// that uses a [HookElement] as its build context. /// All calls of [use] must be made outside of conditional checks and always in the same order. /// /// See [Hook] for more explanations. // ignore: deprecated_member_use, deprecated_member_use_from_same_package R use(Hook hook) => Hook.use(hook); -/// [Hook] is similar to a [StatelessWidget], but is not associated -/// to an [Element]. +/// Allows a [Widget] to create and access its own mutable data +/// without implementing a [State]. /// -/// A [Hook] is typically the equivalent of [State] for [StatefulWidget], -/// with the notable difference that a [HookWidget] can have more than one [Hook]. -/// A [Hook] is created within the [HookState.build] method of a [HookWidget] and the creation -/// must be made unconditionally, always in the same order. +/// Whereas [Widget]s store the immutable configuration for UI components, +/// [Hook]s store immutable configuration for any type of object. +/// The [HookState] of a [Hook] is analogous to the [State] of a [StatefulWidget], +/// and a single [HookWidget] can use more than one [Hook]. +/// +/// Hooks can be used by replacing `extends StatelessWidget` with `extends HookWidget`, +/// or by replacing `Builder()` with `HookBuilder()`. +/// +/// Hook functions must be called unconditionally during the `build()` method, +/// and always in the same order. /// /// ### Good: /// ``` @@ -210,13 +217,17 @@ Calling them outside of build method leads to an unstable state and is therefore /// HookState createState() => _MyHookState(); /// ``` /// - /// The framework can call this method multiple times over the lifetime of a [HookWidget]. For example, + /// The framework can call this method multiple times over the lifetime of a [HookElement]. For example, /// if the hook is used multiple times, a separate [HookState] must be created for each usage. @protected HookState> createState(); } -/// The logic and internal state for a [HookWidget] +/// The logic and internal state of a [Hook]. +/// +/// This class is similar to a [State], but instead of building a [Widget] +/// subtree, the [build] method can return a value of any type, as specified +/// by the "result" type argument `R`. abstract class HookState> with Diagnosticable { /// Equivalent of [State.context] for [HookState] @protected @@ -277,22 +288,19 @@ abstract class HookState> with Diagnosticable { /// Called before a [build] triggered by [markMayNeedRebuild]. /// - /// If [shouldRebuild] returns `false` on all the hooks that called [markMayNeedRebuild] - /// then this aborts the rebuild of the associated [HookWidget]. - /// - /// There is no guarantee that this method will be called after [markMayNeedRebuild] - /// was called. - /// Some situations where [shouldRebuild] will not be called: + /// If [shouldRebuild] returns `false` on all the hooks that called [markMayNeedRebuild], + /// [HookElement.build] will return a cached value instead of rebuilding each [Hook]. /// - /// - [setState] was called - /// - a previous hook's [shouldRebuild] returned `true` - /// - the associated [HookWidget] changed. + /// This method is not evaluated if a previous Hook called [markMayNeedRebuild] + /// and its [shouldRebuild] method returned `true`. + /// Additionally, if [setState], [didUpdateHook], or [HookElement.didChangeDependencies] is called, + /// the build is unconditional and the `shouldRebuild()` call is skipped. bool shouldRebuild() => true; - /// Mark the associated [HookWidget] as **potentially** needing to rebuild. + /// Mark the associated [context] as **potentially** needing to rebuild. /// /// As opposed to [setState], the rebuild is optional and can be cancelled right - /// before `build` is called, by having [shouldRebuild] return false. + /// before [build] is called, by having [shouldRebuild] return false. void markMayNeedRebuild() { if (_element!._isOptionalRebuild != false) { _element! @@ -368,7 +376,10 @@ extension on HookElement { } } -/// An [Element] that uses a [HookWidget] as its configuration. +/// An [Element] that manages [Hook]s by storing the associated [HookState]s in a [LinkedList]. +/// +/// [use] and [useContext] can only be called during this element's [build] method. +/// The `_buildCache` enables the behavior described in [HookState.shouldRebuild]. mixin HookElement on ComponentElement { static HookElement? _currentHookElement; @@ -576,14 +587,12 @@ Type mismatch between hooks: } } -/// A [Widget] that can use a [Hook]. +/// A [Widget] that can use [Hook]s. /// -/// Its usage is very similar to [StatelessWidget]. -/// [HookWidget] does not have any life cycle and only implements -/// the [build] method. +/// Similar to [StatelessWidget], a `HookWidget` is created by extending this class. /// -/// The difference is that it can use a [Hook], which allows a -/// [HookWidget] to store mutable data without implementing a [State]. +/// The [HookWidget.build] method can [use] Hook functions, allowing this widget +/// to store mutable data without implementing a [State]. abstract class HookWidget extends StatelessWidget { /// Initializes [key] for subclasses. const HookWidget({Key? key}) : super(key: key); @@ -596,12 +605,13 @@ class _StatelessHookElement extends StatelessElement with HookElement { _StatelessHookElement(HookWidget hooks) : super(hooks); } -/// A [StatefulWidget] that can use a [Hook]. +/// A [StatefulWidget] that can use [Hook]s. /// -/// Its usage is very similar to that of [StatefulWidget], but uses hooks inside [State.build]. +/// Similar to [StatefulWidget], this widget creates a [State] which +/// can store mutable data. /// -/// The difference is that it can use a [Hook], which allows a -/// [HookWidget] to store mutable data without implementing a [State]. +/// The difference is that [Hook] functions can be called from within [State.build], +/// similar to [HookWidget.build]. abstract class StatefulHookWidget extends StatefulWidget { /// Initializes [key] for subclasses. const StatefulHookWidget({Key? key}) : super(key: key); @@ -614,11 +624,17 @@ class _StatefulHookElement extends StatefulElement with HookElement { _StatefulHookElement(StatefulHookWidget hooks) : super(hooks); } -/// Obtains the [BuildContext] of the building [HookWidget]. +/// Returns the [HookElement] that is currently running its `build()` method. +/// +/// Throws an error if no hook widget is currently building. +/// +/// Generally, Hook functions must be called unconditionally, in the same order. +/// That rule does not apply to `useContext()`, however, since instead of accessing a [Hook], +/// it merely returns the relevant [BuildContext]. BuildContext useContext() { assert( HookElement._currentHookElement != null, - '`useContext` can only be called from the build method of HookWidget', + '`useContext` can only be called while a Hook widget is building.', ); return HookElement._currentHookElement!; } From 4a8a85bf5b1bbd7480cfb7c0a2b8ce6004e2157f Mon Sep 17 00:00:00 2001 From: Nate Wilson Date: Mon, 17 Mar 2025 18:01:56 -0600 Subject: [PATCH 2/5] revert an unhelpful change --- packages/flutter_hooks/lib/src/framework.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/flutter_hooks/lib/src/framework.dart b/packages/flutter_hooks/lib/src/framework.dart index 3939e80..a14e7b9 100644 --- a/packages/flutter_hooks/lib/src/framework.dart +++ b/packages/flutter_hooks/lib/src/framework.dart @@ -10,8 +10,7 @@ bool debugHotReloadHooksEnabled = true; /// Registers a [Hook] and returns its value. /// -/// This function must be called inside the `build` method of a widget -/// that uses a [HookElement] as its build context. +/// [use] must be called within the `build` method of either [HookWidget] or [StatefulHookWidget]. /// All calls of [use] must be made outside of conditional checks and always in the same order. /// /// See [Hook] for more explanations. From 13048ca92a9c0cb5a3793191891f8d72eeeb5fb9 Mon Sep 17 00:00:00 2001 From: Nate Wilson Date: Mon, 17 Mar 2025 18:17:06 -0600 Subject: [PATCH 3/5] update `useContext` description --- packages/flutter_hooks/lib/src/framework.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/flutter_hooks/lib/src/framework.dart b/packages/flutter_hooks/lib/src/framework.dart index a14e7b9..b13d0f3 100644 --- a/packages/flutter_hooks/lib/src/framework.dart +++ b/packages/flutter_hooks/lib/src/framework.dart @@ -627,9 +627,11 @@ class _StatefulHookElement extends StatefulElement with HookElement { /// /// Throws an error if no hook widget is currently building. /// -/// Generally, Hook functions must be called unconditionally, in the same order. -/// That rule does not apply to `useContext()`, however, since instead of accessing a [Hook], -/// it merely returns the relevant [BuildContext]. +/// Most Hook functions call [use] to construct a [Hook] object, +/// but `useContext()` only returns a [BuildContext], so the requirement to +/// make calls unconditionally does not apply here. +/// But for the sake of convention, the best practice is to invoke `use*` functions +/// unconditionally across the board. BuildContext useContext() { assert( HookElement._currentHookElement != null, From 7b9e7d607bb144f7db53eb5369ccd2c891dab07f Mon Sep 17 00:00:00 2001 From: Nate Wilson Date: Mon, 17 Mar 2025 18:21:18 -0600 Subject: [PATCH 4/5] =?UTF-8?q?revert=20`HookWidget`=20=E2=86=92=20`HookEl?= =?UTF-8?q?ement`=20change?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/flutter_hooks/lib/src/framework.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_hooks/lib/src/framework.dart b/packages/flutter_hooks/lib/src/framework.dart index b13d0f3..102d46f 100644 --- a/packages/flutter_hooks/lib/src/framework.dart +++ b/packages/flutter_hooks/lib/src/framework.dart @@ -216,7 +216,7 @@ Calling them outside of build method leads to an unstable state and is therefore /// HookState createState() => _MyHookState(); /// ``` /// - /// The framework can call this method multiple times over the lifetime of a [HookElement]. For example, + /// The framework can call this method multiple times over the lifetime of a [HookWidget]. For example, /// if the hook is used multiple times, a separate [HookState] must be created for each usage. @protected HookState> createState(); From c953b7206f0209e3b404a4259abb1b3dd6d30f1b Mon Sep 17 00:00:00 2001 From: Nate Wilson Date: Sat, 29 Mar 2025 20:00:27 -0600 Subject: [PATCH 5/5] revert `useContext()` description --- packages/flutter_hooks/lib/src/framework.dart | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/flutter_hooks/lib/src/framework.dart b/packages/flutter_hooks/lib/src/framework.dart index 102d46f..fb676a9 100644 --- a/packages/flutter_hooks/lib/src/framework.dart +++ b/packages/flutter_hooks/lib/src/framework.dart @@ -296,7 +296,7 @@ abstract class HookState> with Diagnosticable { /// the build is unconditional and the `shouldRebuild()` call is skipped. bool shouldRebuild() => true; - /// Mark the associated [context] as **potentially** needing to rebuild. + /// Mark the associated [HookWidget] as **potentially** needing to rebuild. /// /// As opposed to [setState], the rebuild is optional and can be cancelled right /// before [build] is called, by having [shouldRebuild] return false. @@ -623,19 +623,11 @@ class _StatefulHookElement extends StatefulElement with HookElement { _StatefulHookElement(StatefulHookWidget hooks) : super(hooks); } -/// Returns the [HookElement] that is currently running its `build()` method. -/// -/// Throws an error if no hook widget is currently building. -/// -/// Most Hook functions call [use] to construct a [Hook] object, -/// but `useContext()` only returns a [BuildContext], so the requirement to -/// make calls unconditionally does not apply here. -/// But for the sake of convention, the best practice is to invoke `use*` functions -/// unconditionally across the board. +/// Obtains the [BuildContext] of the building [HookWidget]. BuildContext useContext() { assert( HookElement._currentHookElement != null, - '`useContext` can only be called while a Hook widget is building.', + '`useContext` can only be called from the build method of HookWidget', ); return HookElement._currentHookElement!; }