Skip to content

Instantly share code, notes, and snippets.

@dipendra-sharma
Last active March 14, 2024 09:14
Show Gist options
  • Save dipendra-sharma/60a35e97d42132895053fb3a5478e5ac to your computer and use it in GitHub Desktop.
Save dipendra-sharma/60a35e97d42132895053fb3a5478e5ac to your computer and use it in GitHub Desktop.
A reusable Flutter widget that efficiently rebuilds when the underlying data changes, supporting various data sources like ValueListenable, Listenable.
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
typedef Selector<T, V> = V Function(T value);
typedef WidgetReBuilder<T> = Widget Function(
BuildContext context, T value, Widget? child);
class ReBuilder<T> extends StatefulWidget {
final WidgetReBuilder<T> builder;
final List<Selector<T, dynamic>> selectors;
final Listenable? notifier;
final Widget? child;
const ReBuilder({
super.key,
required this.builder,
this.selectors = const [],
this.notifier,
this.child,
});
static ReBuilder from2<A, B, T>(
{Key? key,
required ValueListenable<A> notifier1,
required ValueListenable<B> notifier2,
required T Function(A first, B second) combiner,
required WidgetReBuilder<T> builder,
Widget? child}) =>
ReBuilder<T>(key: key,
notifier: CombineLatestValueListenable._([notifier1, notifier2],
(values) => combiner(values[0] as A, values[1] as B)),
builder: builder);
static ReBuilder from3<A, B, C, T>(
{Key? key,
required ValueListenable<A> notifier1,
required ValueListenable<B> notifier2,
required ValueListenable<C> notifier3,
required T Function(A first, B second, C third) combiner,
required WidgetReBuilder<T> builder,
Widget? child}) =>
ReBuilder<T>(key: key,
notifier: CombineLatestValueListenable._(
[notifier1, notifier2, notifier3],
(values) =>
combiner(values[0] as A, values[1] as B, values[2] as C)),
builder: builder);
static ReBuilder from4<A, B, C, D, T>(
{Key? key,
required ValueListenable<A> notifier1,
required ValueListenable<B> notifier2,
required ValueListenable<C> notifier3,
required ValueListenable<D> notifier4,
required T Function(A first, B second, C third, D fourth) combiner,
required WidgetReBuilder<T> builder,
Widget? child}) =>
ReBuilder<T>(key: key,
notifier: CombineLatestValueListenable._(
[notifier1, notifier2, notifier3, notifier4],
(values) => combiner(values[0] as A, values[1] as B,
values[2] as C, values[3] as D)),
builder: builder);
static ReBuilder from5<A, B, C, D, E, T>(
{Key? key,
required ValueListenable<A> notifier1,
required ValueListenable<B> notifier2,
required ValueListenable<C> notifier3,
required ValueListenable<D> notifier4,
required ValueListenable<E> notifier5,
required T Function(A first, B second, C third, D fourth, E fifth)
combiner,
required WidgetReBuilder<T> builder,
Widget? child}) =>
ReBuilder<T>(key: key,
notifier: CombineLatestValueListenable._(
[notifier1, notifier2, notifier3, notifier4, notifier5],
(values) => combiner(values[0] as A, values[1] as B,
values[2] as C, values[3] as D, values[4] as E)),
builder: builder);
@override
State<ReBuilder<T>> createState() => _ReBuilderState<T>();
}
class _ReBuilderState<T> extends State<ReBuilder<T>> {
late T _current;
late T _initial;
List<dynamic>? _lastSelectedValues;
@override
void initState() {
super.initState();
if (widget.notifier is ValueListenable<T>) {
_initial = (widget.notifier as ValueListenable<T>).value;
} else {
_initial = widget.notifier as T;
}
_current = _initial;
_lastSelectedValues = _selectValues(_current);
_setupListener();
}
@override
void didUpdateWidget(covariant ReBuilder<T> oldWidget) {
super.didUpdateWidget(oldWidget);
_updateListener(oldWidget);
}
@override
void dispose() {
_disposeListener();
super.dispose();
}
void _setupListener() {
if (widget.notifier != null) {
widget.notifier!.addListener(_listenableListener);
}
}
void _updateListener(ReBuilder<T> oldWidget) {
if (widget.notifier != oldWidget.notifier) {
oldWidget.notifier?.removeListener(_listenableListener);
widget.notifier?.addListener(_listenableListener);
}
}
void _disposeListener() {
widget.notifier?.removeListener(_listenableListener);
}
void _listenableListener() {
if (widget.notifier is ValueListenable<T>) {
_updateModel((widget.notifier as ValueListenable<T>).value);
} else {
_updateModel(widget.notifier as T);
}
}
void _updateModel(T newValue) {
final newSelectedValues = _selectValues(newValue);
if (widget.selectors.isEmpty ||
!_isEqual(_lastSelectedValues, newSelectedValues)) {
setState(() {
_current = newValue;
_lastSelectedValues = newSelectedValues;
});
}
}
bool _isEqual(List<dynamic>? list1, List<dynamic>? list2) {
if (identical(list1, list2)) return true;
if (list1 == null || list2 == null || list1.length != list2.length)
return false;
return Iterable.generate(list1.length).every((i) => list1[i] == list2[i]);
}
List<dynamic> _selectValues(T value) =>
widget.selectors.map((selector) => selector(value)).toList();
@override
Widget build(BuildContext context) =>
widget.builder(context, _current, widget.child);
}
class CombineLatestValueListenable<T, R> extends ValueNotifier<R> {
final List<ValueListenable<T>> _valueNotifierList;
final List<VoidCallback> _listeners = [];
CombineLatestValueListenable._(
this._valueNotifierList,
R Function(List<T> values) combiner,
) : super(combiner(_valueNotifierList.map((b) => b.value).toList())) {
for (final valueNotifier in _valueNotifierList) {
listener() {
value = combiner(_valueNotifierList.map((b) => b.value).toList());
notifyListeners();
}
valueNotifier.addListener(listener);
_listeners.add(listener);
}
}
static CombineLatestValueListenable from2<A, B, T>(
{required ValueListenable<A> notifier1,
required ValueListenable<B> notifier2,
required T Function(A first, B second) combiner}) =>
CombineLatestValueListenable._([notifier1, notifier2],
(values) => combiner(values[0] as A, values[1] as B));
static CombineLatestValueListenable from3<A, B, C, T>(
{required ValueListenable<A> notifier1,
required ValueListenable<B> notifier2,
required ValueListenable<C> notifier3,
required T Function(A first, B second, C third) combiner}) =>
CombineLatestValueListenable._([notifier1, notifier2, notifier3],
(values) => combiner(values[0] as A, values[1] as B, values[2] as C));
static CombineLatestValueListenable from4<A, B, C, D, T>(
{required ValueListenable<A> notifier1,
required ValueListenable<B> notifier2,
required ValueListenable<C> notifier3,
required ValueListenable<D> notifier4,
required T Function(A first, B second, C third, D fourth)
combiner}) =>
CombineLatestValueListenable._(
[notifier1, notifier2, notifier3, notifier4],
(values) => combiner(
values[0] as A, values[1] as B, values[2] as C, values[3] as D));
static CombineLatestValueListenable from5<A, B, C, D, E, T>(
{required ValueListenable<A> notifier1,
required ValueListenable<B> notifier2,
required ValueListenable<C> notifier3,
required ValueListenable<D> notifier4,
required ValueListenable<E> notifier5,
required T Function(A first, B second, C third, D fourth, E fifth)
combiner}) =>
CombineLatestValueListenable._(
[notifier1, notifier2, notifier3, notifier4, notifier5],
(values) => combiner(values[0] as A, values[1] as B, values[2] as C,
values[3] as D, values[4] as E));
@override
void dispose() {
for (int i = 0; i < _valueNotifierList.length; i++) {
_valueNotifierList[i].removeListener(_listeners[i]);
}
super.dispose();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment