flutter_state_notifier 0.1.0+1 copy "flutter_state_notifier: ^0.1.0+1" to clipboard
flutter_state_notifier: ^0.1.0+1 copied to clipboard

outdated

Flutter bindings for state_notifier (providers, builders)

example/lib/flutter_state_notifier.dart

library flutter_state_notifier;

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'package:provider/single_child_widget.dart';
import 'package:state_notifier/state_notifier.dart';
import 'package:provider/provider.dart' hide Locator;

/// {@template flutter_state_notifier.state_notifier_builder}
/// Listens to a [StateNotifier] and use it builds a widget tree based on the
/// latest value.
///
/// This is similar to [ValueListenableBuilder] for [ValueNotifier].
/// {@endtemplate}
class StateNotifierBuilder<T> extends StatefulWidget {
  /// {@macro flutter_state_notifier.state_notifier_builder}
  const StateNotifierBuilder({
    Key key,
    @required this.builder,
    @required this.stateNotifier,
    this.child,
  })  : assert(builder != null),
        assert(stateNotifier != null),
        super(key: key);

  /// A callback that builds a [Widget] based on the current value of [stateNotifier]
  ///
  /// Cannot be `null`.
  final ValueWidgetBuilder<T> builder;

  /// The listened [StateNotifier].
  ///
  /// Cannot be `null`.
  final StateNotifier<T> stateNotifier;

  /// A cache of a subtree that does not depend on [stateNotifier].
  ///
  /// It will be sent untouched to [builder]. This is useful for performance
  /// optimizations to not rebuild the entire widget tree if it isn't needed.
  final Widget child;

  @override
  _StateNotifierBuilderState<T> createState() =>
      _StateNotifierBuilderState<T>();

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties
      ..add(
        DiagnosticsProperty<StateNotifier<T>>('stateNotifier', stateNotifier),
      )
      ..add(DiagnosticsProperty<Widget>('child', child))
      ..add(ObjectFlagProperty<ValueWidgetBuilder<T>>.has('builder', builder));
  }
}

class _StateNotifierBuilderState<T> extends State<StateNotifierBuilder<T>> {
  T state;
  VoidCallback removeListener;

  @override
  void initState() {
    super.initState();
    _listen(widget.stateNotifier);
  }

  @override
  void didUpdateWidget(StateNotifierBuilder<T> oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.stateNotifier != oldWidget.stateNotifier) {
      _listen(widget.stateNotifier);
    }
  }

  void _listen(StateNotifier<T> notifier) {
    removeListener?.call();
    removeListener = notifier.addListener(_listener);
  }

  void _listener(T value) {
    setState(() => state = value);
  }

  @override
  void dispose() {
    removeListener?.call();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return widget.builder(context, state, widget.child);
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<T>('state', state));
  }
}

// Don't uses a closure to not capture local variables.
Locator _contextToLocator(BuildContext context) {
  return <T>() {
    try {
      return context.read<T>();
    } on ProviderNotFoundException catch (_) {
      throw DependencyNotFoundException<T>();
    }
  };
}

/// A provider for [StateNotifier], which exposes both the controller and its
/// [StateNotifier.state].
///
/// It can be used like most providers.
///
/// Consider the following [StateNotifier]:
/// ```dart
/// class MyNotifier extends StateNotifier<MyValue> {
/// ...
/// }
/// ```
///
/// Then we can expose it to a Flutter app by doing:
///
/// ```dart
/// MultiProvider(
///   providers: [
///     StateNotifierProvider<MyNotifier, MyValue>(create: (_) => MyNotifier()),
///   ],
/// )
/// ```
///
/// This will allow both:
///
/// - `context.watch<MyController>()`
/// - `context.watch<MyValue>()`
///
/// Note that watching `MyController` will not cause the listener to rebuild when
/// [StateNotifier.state] updates.
class StateNotifierProvider<Controller extends StateNotifier<Value>, Value>
    extends SingleChildStatelessWidget {
  /// Creates a [StateNotifier] instance and exposes both the [StateNotifier]
  /// and its [StateNotifier.state] using `provider`.
  ///
  /// **DON'T** use this with an existing [StateNotifier] instance, as removing
  /// the provider from the widget tree will dispose the [StateNotifier].\
  /// Instead consider using [StateNotifierBuilder].
  ///
  /// `create` cannot be `null`.
  // ignore: prefer_const_constructors_in_immutables
  StateNotifierProvider({
    Key key,
    @required Create<Controller> create,
    bool lazy,
    Widget child,
  })  : assert(create != null),
        _create = create,
        _lazy = lazy,
        super(key: key, child: child);

  final Create<Controller> _create;
  final bool _lazy;

  @override
  Widget buildWithChild(BuildContext context, Widget child) {
    return InheritedProvider<Controller>(
      create: (context) {
        final result = _create(context);
        assert(result.onError == null);
        result.onError = (dynamic error, StackTrace stack) {
          FlutterError.reportError(FlutterErrorDetails(
            exception: error,
            stack: stack,
            library: 'flutter_state_notifier',
          ));
        };
        if (result is LocatorMixin) {
          (result as LocatorMixin).read = _contextToLocator(context);
        }
        return result;
      },
      debugCheckInvalidValueType: kReleaseMode
          ? null
          : (value) {
              assert(!value.hasListeners);
            },
      update: (context, controller) {
        if (controller is LocatorMixin) {
          final locatorMixin = controller as LocatorMixin;
          Locator debugPreviousLocator;
          assert(() {
            // ignore: invalid_use_of_protected_member
            debugPreviousLocator = locatorMixin.read;
            locatorMixin.read = <T>() {
              throw StateError("Can't use `read` inside the body of `update");
            };
            return true;
          }());
          // ignore: invalid_use_of_protected_member
          locatorMixin.update(context.watch);
          assert(() {
            locatorMixin.read = debugPreviousLocator;
            return true;
          }());
        }
        return controller;
      },
      dispose: (_, controller) => controller.dispose(),
      child: DeferredInheritedProvider<Controller, Value>(
        lazy: _lazy,
        create: (context) => context.read<Controller>(),
        startListening: (context, setState, controller, _) {
          return controller.addListener(setState);
        },
        child: child,
      ),
    );
  }

  @override
  SingleChildStatelessElement createElement() {
    return _StateNotifierProviderElement(this);
  }
}

class _StateNotifierProviderElement<Controller extends StateNotifier<Value>,
    Value> extends SingleChildStatelessElement {
  _StateNotifierProviderElement(StateNotifierProvider<Controller, Value> widget)
      : super(widget);

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    Element provider;

    void visitor(Element e) {
      if (e.widget is DeferredInheritedProvider<Controller, Value>) {
        provider = e;
      } else {
        e.visitChildren(visitor);
      }
    }

    visitChildren(visitor);

    assert(provider != null);

    provider.debugFillProperties(properties);
  }
}
52
likes
0
pub points
95%
popularity

Publisher

unverified uploader

Flutter bindings for state_notifier (providers, builders)

Repository (GitHub)
View/report issues

License

unknown (LICENSE)

Dependencies

flutter, meta, provider, state_notifier

More

Packages that depend on flutter_state_notifier