Last active
March 26, 2019 18:02
-
-
Save micimize/b47df421b0753e99447d967422584d28 to your computer and use it in GitHub Desktop.
transient state management prototypes for graphql-flutter
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:graphql_flutter/graphql_flutter.dart'; | |
import '../serializers/scalars.dart' show uuidFromObject; | |
/// This prefix works differently than the normalization prefix | |
/// in that it is prepended to the id as '$prefix/$id' | |
final TRANSIENT_STATE_PREFIX = '@entity/transient_state'; | |
final cache = OptimisticCache( | |
dataIdFromObject: uuidFromObject, | |
); |
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 'dart:core'; | |
import 'package:meta/meta.dart'; | |
import '../serializers/graphql.dart' show ToJson; | |
import './_cache.dart'; | |
/// factory producing empty/default state objects | |
typedef T Empty<E, T>(E entity); | |
typedef T Transform<T>(T object); | |
/// a block | |
typedef void VoidCallback(); | |
typedef String IdGetter<E>(E entity); | |
typedef T StateSetter<E extends ToJson, T>( | |
E entity, | |
Transform<T> updateState, | |
); | |
typedef T StateGetter<E extends ToJson, T>( | |
E entity, | |
); | |
enum EntityStatePermissions { READ, WRITE, ALL } | |
class EntityStatePermissionsException implements Exception { | |
String cause; | |
Object value; | |
EntityStatePermissionsException(this.cause, this.value); | |
} | |
/// a `state` getter and `setState` setter for a single focused entity | |
class EntityStateFocus<E extends ToJson, T> { | |
E entity; | |
EntityStateManager<E, T> manager; | |
EntityStateFocus(this.manager, this.entity); | |
T get state => manager.getStateFor(entity); | |
T setState(Transform<T> updateState) => | |
manager.setStateFor(entity, updateState); | |
} | |
class EntityStateManager<E extends ToJson, T> { | |
final IdGetter<E> idGetter; | |
final Empty<E, T> empty; | |
final EntityStatePermissions permissions; | |
/// list of `setStateFor` callbacks. Used by `EntityStateProvider` to trigger flutter `setState` | |
final List<VoidCallback> _onSetStateCallbacks = []; | |
void addListener(VoidCallback callback) { | |
_onSetStateCallbacks.add(callback); | |
} | |
void removeListener(VoidCallback callback) { | |
_onSetStateCallbacks.remove(callback); | |
} | |
EntityStateManager({ | |
@required this.idGetter, | |
@required this.empty, | |
this.permissions = EntityStatePermissions.ALL, | |
}); | |
String _stateKeyFor(E e) { | |
return '$TRANSIENT_STATE_PREFIX/${idGetter(e)}'; | |
} | |
T _getFor(E e) { | |
return cache.read(_stateKeyFor(e)) as T ?? empty(e); | |
} | |
T getStateFor(E e) { | |
if (permissions != EntityStatePermissions.WRITE) { | |
return _getFor(e); | |
} else { | |
throw EntityStatePermissionsException( | |
'Attempted to read with write only permissions', | |
e, | |
); | |
} | |
} | |
T setStateFor(E e, Transform<T> updateState) { | |
if (permissions != EntityStatePermissions.READ) { | |
final T state = updateState(_getFor(e)); | |
cache.write(_stateKeyFor(e), state); | |
_onSetStateCallbacks.forEach((callback) => callback()); | |
// normalization shouldn't effect transient state | |
return state; | |
} else { | |
throw EntityStatePermissionsException( | |
'Attempted to write with read only permissions', | |
e, | |
); | |
} | |
} | |
EntityStateFocus<E, T> focusFor(E e) => EntityStateFocus(this, e); | |
} |
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'; | |
import '../serializers/graphql.dart' show ToJson; | |
import './state_manager.dart' show EntityStateManager; | |
/// provides a `manager` for getting and setting state | |
/// on an entity-wise bases. | |
typedef Widget EntityStateChildBuilder<E extends ToJson, T>( | |
EntityStateManager<E, T> manager, | |
); | |
typedef Widget Curried<E extends ToJson, T>( | |
EntityStateChildBuilder<E, T> builder); | |
class EntityStateProvider<E extends ToJson, T> extends StatefulWidget { | |
final EntityStateManager<E, T> manager; | |
final EntityStateChildBuilder<E, T> builder; | |
const EntityStateProvider({Key key, this.manager, this.builder}) | |
: super(key: key); | |
@override | |
_EntityStateProviderState createState() => _EntityStateProviderState<E, T>(); | |
/// curries a `manager`, producing an easily-usable provider factory | |
static Curried<E, T> curryManager<E extends ToJson, T>( | |
EntityStateManager<E, T> manager) => | |
(EntityStateChildBuilder<E, T> builder) => | |
EntityStateProvider<E, T>(manager: manager, builder: builder); | |
} | |
class _EntityStateProviderState<E extends ToJson, T> | |
extends State<EntityStateProvider<E, T>> { | |
/// the `manager` manages the actual state, | |
/// but we need to `setState()` to trigger rerender; | |
void triggerRerender() { | |
setState(() {}); | |
} | |
@override | |
void initState() { | |
super.initState(); | |
widget.manager.addListener(triggerRerender); | |
} | |
/* | |
@override | |
void didUpdateWidget(EntityStateProvider<E, T> oldWidget) { | |
super.didUpdateWidget(oldWidget); | |
widget.manager.removeListener(oldWidget.triggerRerender); | |
widget.manager.addListener(triggerRerender); | |
} | |
*/ | |
@override | |
void dispose() { | |
widget.manager.removeListener(triggerRerender); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return widget.builder(widget.manager); | |
} | |
} |
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 '../serializers/normalizers.dart' show EventRecord; | |
import '../data_layer/data_layer.dart' | |
show EntityStateManager, EntityStateProvider; | |
typedef TransientEventRecordState EventStateUpdater( | |
TransientEventRecordState eventState); | |
typedef void SetEventState(EventStateUpdater updateEvent); | |
enum EventUIState { | |
UNSUBMITTED, | |
STARTED, | |
DISPLAY_RECORD, | |
EDIT_RECORD, | |
} | |
class TransientEventRecordState { | |
// removed partial completion concept due to complicated logic | |
EventRecordLifecycle completed; | |
EventUIState uiState; | |
Stopwatch timer; | |
DateTime actualStart; | |
DateTime actualEnd; | |
bool expanded; | |
bool loading; | |
Map<TemporalId, Object> metricValues; | |
TransientEventRecordState({ | |
this.metricValues, | |
this.uiState = EventUIState.UNSUBMITTED, | |
this.expanded = true, | |
this.actualStart, | |
this.actualEnd, | |
}) { | |
if (this.metricValues == null) { | |
this.metricValues = Map<TemporalId, Object>(); | |
} | |
} | |
static TransientEventRecordState fromRecord(EventRecord record) { | |
return TransientEventRecordState( | |
actualStart: record.actualStart, | |
actualEnd: record.actualEnd, | |
uiState: record.lifecycle == EventRecordLifecycle.ONGOING | |
? EventUIState.EDIT_RECORD | |
: EventUIState.DISPLAY_RECORD, | |
expanded: false, | |
); | |
} | |
// TODO timer logic is essentially legacy jank | |
void start() { | |
actualStart = DateTime.now(); | |
timer = Stopwatch(); | |
timer.start(); | |
uiState = EventUIState.STARTED; | |
} | |
void complete() { | |
actualEnd = DateTime.now(); | |
timer?.stop(); | |
uiState = EventUIState.DISPLAY_RECORD; | |
completed = EventRecordLifecycle.COMPLETED; | |
} | |
} | |
final _stateManager = | |
EntityStateManager<EventRecord, TransientEventRecordState>( | |
idGetter: (e) => e.uuid, | |
empty: TransientEventRecordState.fromRecord, | |
); | |
final EventRecordStateProvider = | |
EntityStateProvider.curryManager(_stateManager); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
transient_event_record_state.dart
is a usage example, the rest are indata_layer