Skip to content

Instantly share code, notes, and snippets.

@kezhuw
Created November 3, 2017 02:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kezhuw/5063327d99cb553787f5aa5c699dfa7c to your computer and use it in GitHub Desktop.
Save kezhuw/5063327d99cb553787f5aa5c699dfa7c to your computer and use it in GitHub Desktop.
Flutter Store Demo (Mutable InheritedWidget State)
import 'package:meta/meta.dart';
import 'package:flutter/widgets.dart';
final _emptyModuleSet = new Set<Module>();
typedef StateUpdater<T> = void Function(T state);
class Module {
Module({@required this.id, @required Store store, dynamic state}) : _store = store, _state = state;
final dynamic id;
final Store _store;
/// Module state, a null state means that this module is created by a dependent
/// and may be fill later in registration.
dynamic _state;
dynamic get state => _state;
Set<Element> _dependents = new Set<Element>();
void update<T>(StateUpdater<T> stateUpdater) {
if (stateUpdater != null) {
stateUpdater(_state);
}
_store._updateModule(this);
}
void dispose() {
_store._disposeModule(this);
}
}
class Store {
Store(this.name, {Map<Key, dynamic> states}) {
_addStates(states);
}
final String name;
final Map<Key, Module> _modules = {};
final Map<Element, Set<Module>> _dependents = {};
void _addStates(Map<Key, dynamic> states) {
if (states == null) {
return;
}
states.forEach((Key key, dynamic state) {
_modules[key] = new Module(id: key, store: this, state: state);
});
}
void registerModule(Key id, dynamic state) {
assert(state != null);
Module module =_modules.putIfAbsent(id, () => new Module(id: id, store: this));
assert(module._state == null);
module._state = state;
_updateModuleDependents(module);
}
void unregisterModule(Key id) {
Module module = _modules[id];
if (module == null) {
return;
}
_disposeModule(module);
}
void _disposeModule(Module module) {
if (module._state == null) {
return;
}
_modules.remove(module.id);
_updateModuleDependents(module);
}
void _updateModule(Module module) {
_updateModuleDependents(module);
}
/// Expected to call in build scope.
T stateOf<T>(Key id, {@required Element element, @required TypeMatcher<T> stateMatcher}) {
Module module = _modules.putIfAbsent(id, () => new Module(id: id, store: this));
if (module._state != null && !stateMatcher.check(module._state)) {
throw new StoreError("state type mismatch for module $id, got ${module._state.runtimeType}");
}
final dependencies = _dependents.putIfAbsent(element, () => new Set<Module>());
dependencies.add(module);
module._dependents.add(element);
return module._state;
}
/// Can't be called in build scope.
Module moduleOf(Key id) {
return _modules.putIfAbsent(id, () => new Module(id: id, store: this));
}
void _addDependent(Element element) {
_dependents.putIfAbsent(element, () => new Set<Module>());
}
void _removeDependent(Element element) {
Set<Module> dependencies = _dependents.remove(element);
_clearDependent(dependencies, element);
}
bool _containsDependent(Element element) {
return _dependents.containsKey(element);
}
void _clearDependent(Set<Module> modules, Element dependent) {
for (var module in modules) {
module._dependents.remove(dependent);
}
}
void _updateModuleDependents(Module module) {
var elements = module._dependents;
module._dependents = new Set<Element>();
_updateElements(elements);
}
void _updateElements(Set<Element> elements) {
for (var element in elements) {
_clearDependent(_dependents[element], element);
element.didChangeDependencies();
}
}
}
class StoreError extends AssertionError {
StoreError(String message) : super(message);
@override
String get message => super.message;
@override
String toString() => message;
}
class StoreProvider extends InheritedWidget {
StoreProvider({Store store, Widget child}) :
this.store = store,
super(key: new ObjectKey(store), child: child)
;
final Store store;
@override
_StoreElement createElement() => new _StoreElement(this);
/// We don't need this actually, as the entire widget tree will be rebuild after store changed.
@override
bool updateShouldNotify(StoreProvider old) => !identical(store, old.store);
static T stateOf<T>(Key module, {@required BuildContext context, @required TypeMatcher<T> stateMatcher}) {
StoreProvider provider = context.inheritFromWidgetOfExactType(StoreProvider);
if (provider == null) {
throw new StoreError("No StoreProvider in ancester of provided context");
}
return provider.store.stateOf(module, element: context as Element, stateMatcher: stateMatcher);
}
static Module moduleOf(Key module, {@required BuildContext context}) {
StoreProvider provider = context.ancestorInheritedElementForWidgetOfExactType(StoreProvider)?.widget;
if (provider == null) {
throw new StoreError("No StoreProvider in ancester of provided context");
}
return provider.store.moduleOf(module);
}
}
class _StoreElement extends InheritedElement {
_StoreElement(StoreProvider widget) : super(widget);
@override
StoreProvider get widget => super.widget;
@override
Iterable<Element> get dependents {
return widget.store._dependents.keys;
}
@override
void addDependent(Element element) {
widget.store._addDependent(element);
}
@override
void removeDependent(Element element) {
widget.store._removeDependent(element);
}
@override
bool containsDependent(Element element) {
return widget.store._containsDependent(element);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment