import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:nested/nested.dart'; import 'consumer.dart'; import 'provider.dart'; /// Used by providers to determine whether dependents needs to be updated /// when the value exposed changes typedef ShouldRebuild = bool Function(T previous, T next); /// A base class for custom [Selector]. /// /// It works with any [InheritedWidget]. Variants like [Selector] and /// [Selector6] are just syntax sugar to use [Selector0] with [Provider.of]. /// /// But it will **not** work with values /// coming from anything but [InheritedWidget]. /// /// As such, the following: /// /// ```dart /// T value; /// /// return Selector0( /// selector: (_) => value, /// builder: ..., /// ) /// ``` /// /// will still call `builder` again, even if `value` didn't change. class Selector0 extends SingleChildStatefulWidget { /// Both `builder` and `selector` must not be `null`. Selector0({ Key? key, required this.builder, required this.selector, ShouldRebuild? shouldRebuild, Widget? child, }) : _shouldRebuild = shouldRebuild, super(key: key, child: child); /// A function that builds a widget tree from `child` and the last result of /// [selector]. /// /// [builder] will be called again whenever the its parent widget asks for an /// update, or if [selector] return a value that is different from the /// previous one using [operator==]. /// /// Must not be `null`. final ValueWidgetBuilder builder; /// A function that obtains some [InheritedWidget] and map their content into /// a new object with only a limited number of properties. /// /// The returned object must implement [operator==]. /// /// Must not be `null` final T Function(BuildContext) selector; final ShouldRebuild? _shouldRebuild; @override _Selector0State createState() => _Selector0State(); } class _Selector0State extends SingleChildState> { T? value; Widget? cache; Widget? oldWidget; @override Widget buildWithChild(BuildContext context, Widget? child) { final selected = widget.selector(context); final shouldInvalidateCache = oldWidget != widget || (widget._shouldRebuild != null && widget._shouldRebuild!(value as T, selected)) || (widget._shouldRebuild == null && !const DeepCollectionEquality().equals(value, selected)); if (shouldInvalidateCache) { value = selected; oldWidget = widget; cache = Builder( builder: (context) => widget.builder( context, selected, child, ), ); } return cache!; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty('value', value)); } } /// {@template provider.selector} /// An equivalent to [Consumer] that can filter updates by selecting a limited /// amount of values and prevent rebuild if they don't change. /// /// [Selector] will obtain a value using [Provider.of], then pass that value /// to `selector`. That `selector` callback is then tasked to return an object /// that contains only the information needed for `builder` to complete. /// /// By default, [Selector] determines if `builder` needs to be called again /// by comparing the previous and new result of `selector` using /// [DeepCollectionEquality] from the package `collection`. /// /// This behavior can be overridden by passing a custom `shouldRebuild` callback. /// /// **NOTE**: /// The selected value must be immutable, or otherwise [Selector] may think /// nothing changed and not call `builder` again. /// /// As such, it `selector` should return either a collection ([List]/[Map]/[Set]/[Iterable]) /// or a class that override `==`. /// /// Here's an example: /// /// Example 1: /// ///```dart /// Selector( /// selector: (_, foo) => foo.bar, // will rebuild only when `bar` changes /// builder: (_, data, __) { /// return Text('${data.item}'); /// } /// ) ///``` ///In this example `builder` will be called only when `foo.bar` changes. /// /// Example 2: /// /// To select multiple values without having to write a class that implements `==`, /// the easiest solution is to use "Records," available from Dart version 3.0. /// For more information on Records, refer to the [records](https://dart.dev/language/records). /// /// ```dart /// Selector( /// selector: (_, foo) => (item1: foo.item1, item2: foo.item2), /// builder: (_, data, __) { /// return Text('${data.item1} ${data.item2}'); /// }, /// ); /// ``` /// /// In this example, `builder` will be called again only if `foo.item1` or /// `foo.item2` changes. /// /// For generic usage information, see [Consumer]. /// {@endtemplate} class Selector extends Selector0 { /// {@macro provider.selector} Selector({ Key? key, required ValueWidgetBuilder builder, required S Function(BuildContext, A) selector, ShouldRebuild? shouldRebuild, Widget? child, }) : super( key: key, shouldRebuild: shouldRebuild, builder: builder, selector: (context) => selector(context, Provider.of(context)), child: child, ); } /// {@macro provider.selector} class Selector2 extends Selector0 { /// {@macro provider.selector} Selector2({ Key? key, required ValueWidgetBuilder builder, required S Function(BuildContext, A, B) selector, ShouldRebuild? shouldRebuild, Widget? child, }) : super( key: key, shouldRebuild: shouldRebuild, builder: builder, selector: (context) => selector( context, Provider.of(context), Provider.of(context), ), child: child, ); } /// {@macro provider.selector} class Selector3 extends Selector0 { /// {@macro provider.selector} Selector3({ Key? key, required ValueWidgetBuilder builder, required S Function(BuildContext, A, B, C) selector, ShouldRebuild? shouldRebuild, Widget? child, }) : super( key: key, shouldRebuild: shouldRebuild, builder: builder, selector: (context) => selector( context, Provider.of(context), Provider.of(context), Provider.of(context), ), child: child, ); } /// {@macro provider.selector} class Selector4 extends Selector0 { /// {@macro provider.selector} Selector4({ Key? key, required ValueWidgetBuilder builder, required S Function(BuildContext, A, B, C, D) selector, ShouldRebuild? shouldRebuild, Widget? child, }) : super( key: key, shouldRebuild: shouldRebuild, builder: builder, selector: (context) => selector( context, Provider.of(context), Provider.of(context), Provider.of(context), Provider.of(context), ), child: child, ); } /// {@macro provider.selector} class Selector5 extends Selector0 { /// {@macro provider.selector} Selector5({ Key? key, required ValueWidgetBuilder builder, required S Function(BuildContext, A, B, C, D, E) selector, ShouldRebuild? shouldRebuild, Widget? child, }) : super( key: key, shouldRebuild: shouldRebuild, builder: builder, selector: (context) => selector( context, Provider.of(context), Provider.of(context), Provider.of(context), Provider.of(context), Provider.of(context), ), child: child, ); } /// {@macro provider.selector} class Selector6 extends Selector0 { /// {@macro provider.selector} Selector6({ Key? key, required ValueWidgetBuilder builder, required S Function(BuildContext, A, B, C, D, E, F) selector, ShouldRebuild? shouldRebuild, Widget? child, }) : super( key: key, shouldRebuild: shouldRebuild, builder: builder, selector: (context) => selector( context, Provider.of(context), Provider.of(context), Provider.of(context), Provider.of(context), Provider.of(context), Provider.of(context), ), child: child, ); }