Created
November 3, 2017 02:19
-
-
Save kezhuw/5063327d99cb553787f5aa5c699dfa7c to your computer and use it in GitHub Desktop.
Flutter Store Demo (Mutable InheritedWidget State)
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: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