Skip to content

Instantly share code, notes, and snippets.

@micimize
Last active March 26, 2019 18:02
Show Gist options
  • Save micimize/b47df421b0753e99447d967422584d28 to your computer and use it in GitHub Desktop.
Save micimize/b47df421b0753e99447d967422584d28 to your computer and use it in GitHub Desktop.
transient state management prototypes for graphql-flutter
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,
);
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);
}
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);
}
}
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);
@micimize
Copy link
Author

transient_event_record_state.dart is a usage example, the rest are in data_layer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment