Skip to content

Instantly share code, notes, and snippets.

@terryl1900
Last active June 12, 2022 02:20
Show Gist options
  • Save terryl1900/0812b6da58d9225797de9de5a4bee437 to your computer and use it in GitHub Desktop.
Save terryl1900/0812b6da58d9225797de9de5a4bee437 to your computer and use it in GitHub Desktop.
Build Creator - Step 2 - Counter API
import 'package:flutter/material.dart';
// A counter app shows how to expose state mutate APIs. Simple and no magic.
// counter_logic.dart
// Hide the creator with file level private variable.
final _counter = Creator((ref, self) => 0);
// Expose the state related APIs.
int counter(Ref ref, Creator watcher) => ref.watch(_counter, watcher);
void increment(Ref ref) => ref.update<int>(_counter, (n) => n + 1);
void decrement(Ref ref) => ref.update<int>(_counter, (n) => n - 1);
// main.dart
void main() {
runApp(CreatorGraph(child: const MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Counter example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Watcher((context, ref, self) {
return Text('${counter(ref, self)}');
}),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: () => increment(context.ref),
child: const Text('+1'),
),
TextButton(
onPressed: () => decrement(context.ref),
child: const Text('-1'),
),
],
),
],
),
),
),
);
}
}
// -------------------------- Creator Library ----------------------------------
/// Creator creates a stream of T.
class Creator<T> {
const Creator(this.create);
final T Function(Ref ref, Creator<T> self) create;
Element<T> _createElement(Ref ref) => Element<T>(ref, this);
}
// Element holds the state for creator.
class Element<T> {
Element(this.ref, this.creator) : state = creator.create(ref, creator);
final Ref ref;
final Creator<T> creator;
T state;
void recreate() {
final newState = creator.create(ref, creator);
if (newState != state) {
state = newState;
ref._onStateChange(creator);
}
}
}
/// Ref holds the creator states and dependencies.
class Ref {
Ref();
/// Elements which hold state.
final Map<Creator, Element> _elements = {};
/// Dependency graph. Think this as a directional graph.
/// A -> [B, C] means if A changes, B and C need change too.
final Map<Creator, Set<Creator>> _graph = {};
/// Get or create an element for creator.
Element _element<T>(Creator creator) =>
_elements.putIfAbsent(creator, () => creator._createElement(this));
/// Add an edge creator -> watcher to the graph, then return creator's state.
T watch<T>(Creator<T> creator, Creator? watcher) {
if (watcher != null) {
(_graph[creator] ??= {}).add(watcher);
}
return _element<T>(creator).state;
}
/// Set state of the creator.
void set<T>(Creator<T> creator, T state) {
final element = _element<T>(creator);
if (state != element.state) {
element.state = state;
_onStateChange(creator);
}
}
/// Set state of creator using an update function. See [set].
void update<T>(Creator<T> creator, T Function(T) update) =>
set<T>(creator, update(_element(creator).state));
/// Force creator to recreate its state.
void recreate(Creator creator) {
_element(creator).recreate();
}
/// Delete the creator if it has no watcher. Also delete other creators who
/// loses all their watchers.
void dispose(Creator creator) {
if ((_graph[creator] ?? {}).isNotEmpty) {
return; // The creator is being watched by someone, cannot dispose it.
}
_elements.remove(creator);
_graph.remove(creator);
for (final c in _elements.keys.toSet()) {
if ((_graph[c] ?? {}).contains(creator)) {
_graph[c]!.remove(creator);
dispose(c); // Dispose c if creator is the only watcher of c.
}
}
}
/// Propagate state changes.
void _onStateChange(Creator creator) {
for (final c in _graph[creator] ?? {}) {
_element(c).recreate();
}
}
}
/// CreatorGraph simply expose Ref through context.
class CreatorGraph extends InheritedWidget {
CreatorGraph({Key? key, required Widget child})
: super(key: key, child: child);
final Ref ref = Ref();
static CreatorGraph of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<CreatorGraph>()!;
@override
bool updateShouldNotify(CreatorGraph oldWidget) => ref != oldWidget.ref;
}
extension ContextRef on BuildContext {
Ref get ref => CreatorGraph.of(this).ref;
}
/// Watch creators to build a widget or to perform other action.
class Watcher extends StatefulWidget {
const Watcher(this.builder, {Key? key}) : super(key: key);
/// Allows watching creators to populate a widget.
final Widget Function(BuildContext context, Ref ref, Creator self)? builder;
@override
State<Watcher> createState() => _WatcherState();
}
class _WatcherState extends State<Watcher> {
late Creator<Widget> builder;
late Ref ref;
@override
void didChangeDependencies() {
super.didChangeDependencies();
ref = CreatorGraph.of(context).ref; // Save ref to use in dispose.
builder = Creator((ref, self) {
setState(() {});
return widget.builder!(context, ref, self);
});
}
@override
void dispose() {
ref.dispose(builder);
super.dispose();
}
@override
Widget build(BuildContext context) {
ref.recreate(builder);
return ref.watch(builder, null);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment