Last active
March 14, 2024 09:14
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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