Skip to content

Instantly share code, notes, and snippets.

@roipeker
Last active September 1, 2021 21:34
Show Gist options
  • Save roipeker/42a6155a72501b091a9e54428cf7b3ac to your computer and use it in GitHub Desktop.
Save roipeker/42a6155a72501b091a9e54428cf7b3ac to your computer and use it in GitHub Desktop.
sample reactive ValueNotifier + StateWidget( Widget build() inside Widget)
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:redo_provider/notifier/notifier.dart';
import 'package:redo_provider/state_widget.dart';
class AboutPage extends StateWidget<AboutPageState> {
const AboutPage({Key? key}) : super(key: key);
@override
createState() => AboutPageState();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Hello ${state.name}!'),
const Divider(),
Text('hot reloads: ${state.hotReloads}'),
const Divider(),
Observer(() => Text('ticker: ${state.tickerTime}')),
],
),
);
}
SnackBar buildSnackbar() {
return SnackBar(
content: Text('Ticker disposed @${state.tickerTime}'),
duration: Duration(milliseconds: 1200),
action: SnackBarAction(
label: 'Widget mounted?',
onPressed: state.onSnackbarAction,
),
);
}
}
class AboutPageState extends StateController<AboutPage>
with SingleTickerProviderStateMixin {
final name = 'roi';
final hotReloads = NotifierValue(0);
final tickerTime = NotifierValue(Duration.zero);
late final Ticker _ticker = createTicker((e) => tickerTime(e));
@override
void initState() {
_ticker.start();
super.initState();
}
@override
void reassemble() {
hotReloads.value++;
super.reassemble();
}
late ScaffoldMessengerState _snackbarHolder;
@override
void didChangeDependencies() {
_snackbarHolder = ScaffoldMessenger.of(context);
super.didChangeDependencies();
}
@override
void dispose() {
_ticker.dispose();
Future.microtask(_showSnackbar);
super.dispose();
}
FutureOr _showSnackbar() =>
_snackbarHolder.showSnackBar(widget.buildSnackbar());
void onSnackbarAction() {
print('Widget still mounted? $mounted');
}
}
import 'package:flutter/material.dart';
import 'notifier/notifier.dart';
class ContactPage extends StatefulWidget {
const ContactPage({Key? key}) : super(key: key);
@override
_ContactPageState createState() => _ContactPageState();
}
class _ContactPageState extends State<ContactPage> with DisposerMixin {
final user2 = NotifierValue<User?>(null);
late final user = User('roi', 34).obs(
disposer: this,
);
late final count = 0.obs(
disposer: this,
onChange: onCountChange,
);
final active = false.obs();
late Stream<int> counterStream;
@override
void initState() {
/// listen to dispose()
user2.hookDispose(this);
super.initState();
counterStream = Stream<int>.periodic(const Duration(seconds: 1), (x) => x)
.take(15)
.asBroadcastStream();
count.bindStream(counterStream);
}
void onCountChange(int value) {
print('count changed: $value');
if (value > 8) {
count.closeStream(counterStream);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Observer((){
return Text('user 2 : $user2');
}),
Divider(),
Observer(
() => Switch(value: active(), onChanged: active),
),
Divider(),
Observer(
() => Text('Hello contact $count'),
),
Divider(),
Observer(
() => Text('Other contact ${count() - 2}'),
),
Divider(),
Observer(() {
print('rebuild user obs()');
return Text('$user');
// return Text('hola!');
}),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
count.value += 2;
active.toggle();
user2.value = User('Emilce', 36);
user.update((user) => user.copyWith(name: 'Ismail'));
},
),
);
}
}
class User with IValueNotifier {
final String name;
final int age;
const User(this.name, this.age);
User copyWith({String? name, int? age}) {
return User(name ?? this.name, age ?? this.age);
}
@override
String toString() => 'User{name: $name, age: $age}';
/// for equality.
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is User &&
runtimeType == other.runtimeType &&
name == other.name &&
age == other.age;
@override
int get hashCode => name.hashCode ^ age.hashCode;
Map<String, dynamic> toJson() => {'name': name, 'age': age};
}
part of notifier;
extension NotifierIntX on int {
NotifierValue<int> obs({
DisposerNotifier? disposer,
ValueChanged<int>? onChange,
}) {
final o = NotifierValue<int>(this)..hookDispose(disposer);
if (onChange != null) {
o.addValueListener(onChange);
}
return o;
}
}
extension NotifierDoubleX on double {
NotifierValue<double> obs({
DisposerNotifier? disposer,
ValueChanged<double>? onChange,
}) {
final o = NotifierValue<double>(this)..hookDispose(disposer);
if (onChange != null) {
o.addValueListener(onChange);
}
return o;
}
}
extension NotifierStringX on String {
NotifierValue<String> obs({
DisposerNotifier? disposer,
ValueChanged<String>? onChange,
}) {
final o = NotifierValue<String>(this)..hookDispose(disposer);
if (onChange != null) {
o.addValueListener(onChange);
}
return o;
}
}
extension NotifierBoolX on bool {
NotifierValue<bool> rvn({
DisposerNotifier? disposer,
ValueChanged<bool>? onChange,
}) {
final o = NotifierValue<bool>(this);
o.hookDispose(disposer);
if (onChange != null) {
o.addValueListener(onChange);
}
return o;
}
}
extension NotifierValueBoolX on NotifierValue<bool> {
bool get isTrue => value;
bool get isFalse => !isTrue;
bool operator &(bool other) => other && value;
bool operator |(bool other) => other || value;
bool operator ^(bool other) => !other == value;
void toggle() {
value = !value;
}
}
extension NotifierValueInterfaseX<T extends IValueNotifier> on T {
NotifierValue<T> obs({
DisposerNotifier? disposer,
ValueChanged<T>? onChange,
}) {
final o = NotifierValue<T>(this)..hookDispose(disposer);
if (onChange != null) {
o.addValueListener(onChange);
}
return o;
}
}
class NotifierValue<T> extends ValueNotifier<T> {
static _ObxNotifier? _proxyNotifier;
Map<Stream, StreamSubscription> _subscriptions = {};
DisposerNotifier? _disposer;
NotifierValue<T> hookDispose(DisposerNotifier? instance) {
_disposer = instance;
_disposer?.addListener(_onDispose);
return this;
}
void _onDispose() {
_disposer?.removeListener(_onDispose);
dispose();
}
NotifierValue(T value) : super(value);
final Expando<VoidCallback> _valueListeners = Expando();
void addValueListener(ValueChanged<T> listener) {
_valueListeners[listener] = () {
listener(super.value);
};
addListener(_valueListeners[listener]!);
}
void removeValueListener(ValueChanged<T> listener) {
if (_valueListeners[listener] != null) {
removeListener(_valueListeners[listener]!);
_valueListeners[listener] = null;
}
}
FutureOr closeStream(Stream<T> stream) {
if (_subscriptions.containsKey(stream)) {
return _subscriptions.remove(stream)!.cancel();
}
}
void bindStream(Stream<T> stream) {
late StreamSubscription subscription;
subscription = stream.asBroadcastStream().listen((event) {
this.value = event;
}, onDone: () {
subscription.cancel();
_subscriptions.remove(subscription);
});
_subscriptions[stream] = subscription;
}
@override
String toString() => '$value';
T call([T? newValue]) {
if (newValue != null && newValue != value) {
value = newValue;
}
return value;
}
@override
set value(T newValue) {
if (newValue != super.value) {
super.value = newValue;
}
}
@override
T get value {
if (NotifierValue._proxyNotifier != null) {
NotifierValue._proxyNotifier!.add(this);
}
return super.value;
}
void update(T Function(T value) fn) {
value = fn(super.value);
}
@override
void dispose() {
if (_proxyNotifier != null) {
_proxyNotifier!.remove(this);
}
for (final subscription in _subscriptions.values) {
subscription.cancel();
}
_subscriptions.clear();
super.dispose();
}
}
class NotifierValueInt extends NotifierValue<int> {
NotifierValueInt(int value) : super(value);
}
abstract class IValueNotifier<T> {
/// complies with obs().
}
mixin DisposerMixin<T extends StatefulWidget> on State<T>
implements DisposerNotifier {
final _disposer = _DisposerNotifier();
void addListener(VoidCallback fn) => _disposer.addListener(fn);
void removeListener(VoidCallback fn) => _disposer.removeListener(fn);
void dispose() {
_disposer.dispose();
super.dispose();
}
}
class _DisposerNotifier extends ChangeNotifier {
@override
void dispose() {
notifyListeners();
super.dispose();
}
}
abstract class DisposerNotifier {
final _disposer = _DisposerNotifier();
void addListener(VoidCallback fn) => _disposer.addListener(fn);
void removeListener(VoidCallback fn) => _disposer.removeListener(fn);
void dispose() {
_disposer.dispose();
}
}
extension ValueNotifierX<T> on ValueListenable<T> {
String get string => '$value';
Widget widget<T>({
Widget? child,
required ValueWidgetBuilder<T> builder,
}) {
return ValueListenableBuilder<T>(
valueListenable: this as ValueListenable<T>,
builder: builder,
child: child,
);
}
}
class Observer extends StatelessWidget {
final Widget Function() builder;
const Observer(
this.builder, {
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ObserverBuilder(
builder: (_, __) => builder(),
);
}
}
class ObserverBuilder extends StatefulWidget {
final TransitionBuilder builder;
final Widget? child;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties..add(ObjectFlagProperty<Function>.has('builder', builder));
}
const ObserverBuilder({
Key? key,
required this.builder,
this.child,
}) : super(key: key);
@override
_ObserverBuilderState createState() => _ObserverBuilderState();
}
class _ObserverBuilderState extends State<ObserverBuilder> {
late _ObxNotifier notifier = _ObxNotifier(update);
void update() {
if (mounted) {
setState(() {});
}
}
@override
void dispose() {
notifier.dispose(false);
super.dispose();
}
Widget notifyChild(BuildContext context, Widget? child) {
final oldNotifier = NotifierValue._proxyNotifier;
NotifierValue._proxyNotifier = notifier;
final result = widget.builder(context, child);
if (!notifier.canUpdate) {
NotifierValue._proxyNotifier = oldNotifier;
throw """
[NotifierValue] improper use of Observer() or ObserverBuilder() detected.
Use [NotifierValue](s) directly in the scope of the builder().
If you need to update a parent widget and a child widget, wrap them separately in Observer() or ObserverBuilder().
""";
}
NotifierValue._proxyNotifier = oldNotifier;
return result;
}
@override
void reassemble() {
notifier.dispose(true);
super.reassemble();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: notifier.notifications,
builder: (_, child) => notifyChild(context, child),
child: widget.child,
);
}
}
class _ObxNotifier {
static final emptyListener = ChangeNotifier();
Listenable notifications = emptyListener;
final VoidCallback stateSetter;
final _notis = <NotifierValue>{};
Set<NotifierValue>? _cachedNotis;
bool _dirtyWidget = false;
_ObxNotifier(this.stateSetter);
bool get canUpdate => _notis.isNotEmpty;
void add(NotifierValue noti) {
if (!_notis.contains(noti)) {
if (_cachedNotis != null && _dirtyWidget && _cachedNotis!.isNotEmpty) {
final _cached = _cachedNotis!;
for (var m in _cached) {
m.dispose();
}
_notis.removeAll(_cached);
_cached.clear();
_cachedNotis = null;
}
_notis.add(noti);
_updateCollection();
}
}
void remove(NotifierValue motion) {
if (_notis.contains(motion)) {
_notis.remove(motion);
_updateCollection();
}
}
void _updateCollection() {
if (_notis.isEmpty) {
notifications = emptyListener;
} else {
notifications = Listenable.merge(_notis.toList(growable: false));
}
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) => stateSetter());
}
void dispose([bool reassembling = false]) {
final _buffer = List.of(_notis);
// _buffer.forEach((e) {
// if (e is _ValueNoti && reassembling) {
// e.dispose();
// }
// });
notifications = emptyListener;
_notis.clear();
_buffer.clear();
_cachedNotis?.clear();
_cachedNotis = null;
}
}
import 'package:flutter/widgets.dart';
abstract class StateWidget<T extends State> extends StatefulWidget {
const StateWidget({Key? key}) : super(key: key);
Widget build(BuildContext context);
T get state => StateElement._elements[this] as T;
@override
StateElement createElement() => StateElement(this);
@override
T createState();
}
class StateElement extends StatefulElement {
static final _elements = Expando('State Controllers');
StateElement(StateWidget widget) : super(widget) {
_elements[widget] = state;
}
@override
void update(StatefulWidget newWidget) {
_elements[newWidget] = state;
super.update(newWidget);
}
@override
StateWidget get widget => super.widget as StateWidget;
@override
Widget build() => widget.build(this);
}
class StateController<T extends StateWidget> extends State<T> {
@protected
@override
Widget build(BuildContext context) {
throw "$runtimeType\.build() is invalid. Use <StateWidget.build()> instead.";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment