Skip to content

Instantly share code, notes, and snippets.

@purplenoodlesoop
Last active December 3, 2022 18:42
Show Gist options
  • Save purplenoodlesoop/bd0695d3b308bcf77547ec89e924071b to your computer and use it in GitHub Desktop.
Save purplenoodlesoop/bd0695d3b308bcf77547ec89e924071b to your computer and use it in GitHub Desktop.
Inherited widget with granular updates that can be used in `didChangeDependencies`
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
A id<A>(A a) => a;
bool _unaryTrue(dynamic _) => true;
bool _notEquals(dynamic first, dynamic second) =>
!const DeepCollectionEquality().equals(first, second);
class InheritedSelector<T> extends InheritedWidget {
final T data;
const InheritedSelector({
required this.data,
required super.child,
super.key,
});
static R select<T, R>(
BuildContext context,
R Function(T data) selector, {
bool listen = false,
}) {
if (listen) {
context.dependOnInheritedWidgetOfExactType<InheritedSelector<T>>(
aspect: selector,
);
}
final widget = context
.getElementForInheritedWidgetOfExactType<InheritedSelector<T>>()
?.widget as InheritedSelector<T>?;
return selector(widget!.data);
}
static T of<T>(BuildContext context, {bool listen = false}) => select<T, T>(
context,
id<T>,
listen: listen,
);
@override
bool updateShouldNotify(InheritedSelector<T> oldWidget) => _notEquals(
data,
oldWidget.data,
);
@override
InheritedSelectorElement<T> createElement() => InheritedSelectorElement(this);
}
typedef _Selector<T> = dynamic Function(T value);
typedef _Dependencies<T> = Set<_Selector<T>>;
class InheritedSelectorElement<T> extends InheritedElement {
InheritedSelectorElement(InheritedSelector<T> widget) : super(widget);
bool _shouldClearDependencies = false;
bool _shouldScheduleResetFlags = true;
@override
InheritedSelector<T> get widget => super.widget as InheritedSelector<T>;
_Dependencies<T>? _currentDependencies(Element dependent) =>
(getDependencies(dependent) as _Dependencies<T>?);
void _clearDependencies(_Dependencies<T>? dependencies) {
if (dependencies != null && dependencies.isNotEmpty) {
dependencies.clear();
}
}
Future<void> _scheduleResetFlags() => Future.microtask(() {
_shouldClearDependencies = true;
_shouldScheduleResetFlags = true;
});
bool _shouldNotify(InheritedSelector<T> oldWidget, Element dependent) =>
_currentDependencies(dependent)
?.map(
(selector) => _notEquals(
selector(widget.data),
selector(oldWidget.data),
),
)
.any(id) ??
false;
@override
void updateDependencies(Element dependent, Object? aspect) {
final dependencies = _currentDependencies(dependent);
if (!(dependencies?.contains(_unaryTrue) ?? false)) {
if (_shouldClearDependencies) {
_shouldClearDependencies = false;
_clearDependencies(dependencies);
}
if (_shouldScheduleResetFlags) {
_shouldScheduleResetFlags = false;
_scheduleResetFlags();
}
setDependencies(
dependent,
(dependencies ?? <_Selector<T>>{})
..add((aspect as _Selector<T>?) ?? _unaryTrue),
);
}
}
@override
void notifyDependent(InheritedSelector<T> oldWidget, Element dependent) {
if (_shouldNotify(oldWidget, dependent)) dependent.didChangeDependencies();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment