Last active
June 12, 2022 02:20
-
-
Save terryl1900/6b105bd3cb1d2a2c6ed36cb2c9a270b9 to your computer and use it in GitHub Desktop.
Build Creator - Step 2 - Weather
This file contains 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:flutter/material.dart'; | |
// Simple weather app shows splitting backend/logic/ui code, building logic | |
// with Creator for both sync and async behavior. | |
// repo.dart | |
// Pretend calling a backend service to get fahrenheit temperature. | |
Future<int> getFahrenheit(String city) async { | |
await Future.delayed(const Duration(milliseconds: 100)); | |
return 60 + city.hashCode % 20; | |
} | |
// logic.dart | |
// Simple creators bind to UI. | |
final cityCreator = Creator((ref, self) => 'London'); | |
final unitCreator = Creator((ref, self) => 'Fahrenheit'); | |
// Get temperature with data from backend and user's unit selection. | |
final temperatureCreator = Creator((ref, self) async { | |
final f = await getFahrenheit(ref.watch(cityCreator, self)); | |
final unit = ref.watch(unitCreator, self); | |
return unit == 'Fahrenheit' ? '$f F' : '${f2c(f)} C'; | |
}); | |
// Fahrenheit to celsius converter. | |
int f2c(int f) => ((f - 32) * 5 / 9).round(); | |
// 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('Weather example')), | |
body: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
Watcher((context, ref, self) { | |
return FutureBuilder<String>( | |
future: ref.watch(temperatureCreator, self), | |
builder: (context, snapshot) { | |
return snapshot.data != null | |
? Text(snapshot.data!) | |
: const CircularProgressIndicator(); | |
}); | |
}), | |
const SizedBox(height: 20), | |
Watcher( | |
(context, ref, self) { | |
final city = ref.watch(cityCreator, self); | |
return Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: ['London', 'Pairs', 'Rome'] | |
.map((e) => TextButton( | |
style: TextButton.styleFrom( | |
backgroundColor: city == e ? Colors.lime : null), | |
onPressed: () => ref.set(cityCreator, e), | |
child: Text(e))) | |
.toList(), | |
); | |
}, | |
), | |
const SizedBox(height: 20), | |
Watcher( | |
(context, ref, self) { | |
final unit = ref.watch(unitCreator, self); | |
return Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: ['Fahrenheit', 'Celsius'] | |
.map((e) => TextButton( | |
style: TextButton.styleFrom( | |
backgroundColor: unit == e ? Colors.lime : null), | |
onPressed: () => ref.set(unitCreator, e), | |
child: Text(e))) | |
.toList(), | |
); | |
}, | |
) | |
], | |
), | |
), | |
); | |
} | |
} | |
// -------------------------- 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