import 'dart:collection';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import '../../formats.dart';
import 'content.dart';
typedef LocalWidgetBuilder = Widget Function(BuildContext context, DataSource source);
typedef _RemoteWidgetBuilder = _CurriedWidget Function(DynamicMap builderArg);
typedef RemoteEventHandler = void Function(String eventName, DynamicMap eventArguments);
typedef HandlerGenerator<T extends Function> = T Function(HandlerTrigger trigger);
typedef HandlerTrigger = void Function([DynamicMap? extraArguments]);
class RemoteFlutterWidgetsException implements Exception {
const RemoteFlutterWidgetsException(this.message);
final String message;
@override
String toString() => message;
}
abstract class DataSource {
T? v<T extends Object>(List<Object> argsKey);
bool isList(List<Object> argsKey);
int length(List<Object> argsKey);
bool isMap(List<Object> argsKey);
Widget child(List<Object> argsKey);
Widget? optionalChild(List<Object> argsKey);
List<Widget> childList(List<Object> argsKey);
Widget builder(List<Object> argsKey, DynamicMap builderArg);
Widget? optionalBuilder(List<Object> argsKey, DynamicMap builderArg);
VoidCallback? voidHandler(List<Object> argsKey, [ DynamicMap? extraArguments ]);
T? handler<T extends Function>(List<Object> argsKey, HandlerGenerator<T> generator);
}
class LocalWidgetLibrary extends WidgetLibrary {
LocalWidgetLibrary(this._widgets);
final Map<String, LocalWidgetBuilder> _widgets;
@protected
LocalWidgetBuilder? findConstructor(String name) {
return _widgets[name];
}
UnmodifiableMapView<String, LocalWidgetBuilder> get widgets {
return UnmodifiableMapView<String, LocalWidgetBuilder>(_widgets);
}
}
class _ResolvedConstructor {
const _ResolvedConstructor(this.fullName, this.constructor);
final FullyQualifiedWidgetName fullName;
final Object constructor;
}
class Runtime extends ChangeNotifier {
Runtime();
final Map<LibraryName, WidgetLibrary> _libraries = <LibraryName, WidgetLibrary>{};
void update(LibraryName name, WidgetLibrary library) {
_libraries[name] = library;
_clearCache();
}
void clearLibraries() {
_libraries.clear();
_clearCache();
}
UnmodifiableMapView<LibraryName, WidgetLibrary> get libraries {
return UnmodifiableMapView<LibraryName, WidgetLibrary>(Map<LibraryName, WidgetLibrary>.from(_libraries));
}
final Map<FullyQualifiedWidgetName, _ResolvedConstructor?> _cachedConstructors = <FullyQualifiedWidgetName, _ResolvedConstructor?>{};
final Map<FullyQualifiedWidgetName, _CurriedWidget> _widgets = <FullyQualifiedWidgetName, _CurriedWidget>{};
void _clearCache() {
_cachedConstructors.clear();
_widgets.clear();
notifyListeners();
}
Widget build(
BuildContext context,
FullyQualifiedWidgetName widget,
DynamicContent data,
RemoteEventHandler remoteEventTarget,
) {
_CurriedWidget? boundWidget = _widgets[widget];
if (boundWidget == null) {
_checkForImportLoops(widget.library);
boundWidget = _applyConstructorAndBindArguments(
widget,
const <String, Object?>{},
const <String, Object?>{},
-1,
<FullyQualifiedWidgetName>{},
null,
);
_widgets[widget] = boundWidget;
}
return boundWidget.build(context, data, remoteEventTarget, const <_WidgetState>[]);
}
static BlobNode? blobNodeFor(BuildContext context) {
if (context.widget is! _Widget) {
context.visitAncestorElements((Element element) {
if (element.widget is _Widget) {
context = element;
return false;
}
return true;
});
}
if (context.widget is! _Widget) {
return null;
}
return (context.widget as _Widget).curriedWidget;
}
void _checkForImportLoops(LibraryName name, [ Set<LibraryName>? visited ]) {
final WidgetLibrary? library = _libraries[name];
if (library is RemoteWidgetLibrary) {
visited ??= <LibraryName>{};
visited.add(name);
for (final Import import in library.imports) {
final LibraryName dependency = import.name;
if (visited.contains(dependency)) {
final List<LibraryName> path = <LibraryName>[dependency];
for (final LibraryName name in visited.toList().reversed) {
if (name == dependency) {
break;
}
path.add(name);
}
if (path.length == 1) {
assert(path.single == dependency);
throw RemoteFlutterWidgetsException('Library $dependency depends on itself.');
} else {
throw RemoteFlutterWidgetsException('Library $dependency indirectly depends on itself via ${path.reversed.join(" which depends on ")}.');
}
}
_checkForImportLoops(dependency, visited.toSet());
}
}
}
_ResolvedConstructor? _findConstructor(FullyQualifiedWidgetName fullName) {
final _ResolvedConstructor? result = _cachedConstructors[fullName];
if (result != null) {
return result;
}
final WidgetLibrary? library = _libraries[fullName.library];
if (library is RemoteWidgetLibrary) {
for (final WidgetDeclaration constructor in library.widgets) {
if (constructor.name == fullName.widget) {
return _cachedConstructors[fullName] = _ResolvedConstructor(fullName, constructor);
}
}
for (final Import import in library.imports) {
final LibraryName dependency = import.name;
final _ResolvedConstructor? result = _findConstructor(FullyQualifiedWidgetName(dependency, fullName.widget));
if (result != null) {
return _cachedConstructors[fullName] = result;
}
}
} else if (library is LocalWidgetLibrary) {
final LocalWidgetBuilder? constructor = library.findConstructor(fullName.widget);
if (constructor != null) {
return _cachedConstructors[fullName] = _ResolvedConstructor(fullName, constructor);
}
} else {
assert(library is Null);
}
_cachedConstructors[fullName] = null;
return null;
}
Iterable<LibraryName> _findMissingLibraries(LibraryName library) sync* {
final WidgetLibrary? root = _libraries[library];
if (root == null) {
yield library;
return;
}
if (root is LocalWidgetLibrary) {
return;
}
root as RemoteWidgetLibrary;
for (final Import import in root.imports) {
yield* _findMissingLibraries(import.name);
}
}
_CurriedWidget _applyConstructorAndBindArguments(
FullyQualifiedWidgetName fullName,
DynamicMap arguments,
DynamicMap widgetBuilderScope,
int stateDepth,
Set<FullyQualifiedWidgetName> usedWidgets,
BlobNode? source,
) {
final _ResolvedConstructor? widget = _findConstructor(fullName);
if (widget != null) {
if (widget.constructor is WidgetDeclaration) {
if (usedWidgets.contains(widget.fullName)) {
return _CurriedLocalWidget.error(
fullName,
'Widget loop: Tried to call ${widget.fullName} constructor reentrantly.',
)..propagateSource(source);
}
usedWidgets = usedWidgets.toSet()..add(widget.fullName);
final WidgetDeclaration constructor = widget.constructor as WidgetDeclaration;
final int newDepth;
if (constructor.initialState != null) {
newDepth = stateDepth + 1;
} else {
newDepth = stateDepth;
}
Object result = _bindArguments(
widget.fullName,
constructor.root,
arguments,
widgetBuilderScope,
newDepth,
usedWidgets,
);
if (result is Switch) {
result = _CurriedSwitch(
widget.fullName,
result,
arguments,
widgetBuilderScope,
constructor.initialState,
)..propagateSource(result);
} else {
result as _CurriedWidget;
if (constructor.initialState != null) {
result = _CurriedRemoteWidget(
widget.fullName,
result,
arguments,
widgetBuilderScope,
constructor.initialState,
)..propagateSource(result);
}
}
return result as _CurriedWidget;
}
assert(widget.constructor is LocalWidgetBuilder);
return _CurriedLocalWidget(
widget.fullName,
widget.constructor as LocalWidgetBuilder,
arguments,
widgetBuilderScope,
)..propagateSource(source);
}
final Set<LibraryName> missingLibraries = _findMissingLibraries(fullName.library).toSet();
if (missingLibraries.isNotEmpty) {
return _CurriedLocalWidget.error(
fullName,
'Could not find remote widget named ${fullName.widget} in ${fullName.library}, '
'possibly because some dependencies were missing: ${missingLibraries.join(", ")}',
)..propagateSource(source);
}
return _CurriedLocalWidget.error(fullName, 'Could not find remote widget named ${fullName.widget} in ${fullName.library}.')
..propagateSource(source);
}
Object _bindArguments(
FullyQualifiedWidgetName context,
Object node, Object arguments,
DynamicMap widgetBuilderScope,
int stateDepth,
Set<FullyQualifiedWidgetName> usedWidgets,
) {
if (node is ConstructorCall) {
final DynamicMap subArguments = _bindArguments(
context,
node.arguments,
arguments,
widgetBuilderScope,
stateDepth,
usedWidgets,
) as DynamicMap;
return _applyConstructorAndBindArguments(
FullyQualifiedWidgetName(context.library, node.name),
subArguments,
widgetBuilderScope,
stateDepth,
usedWidgets,
node,
);
}
if (node is WidgetBuilderDeclaration) {
return (DynamicMap widgetBuilderArg) {
final DynamicMap newWidgetBuilderScope = <String, Object?> {
...widgetBuilderScope,
node.argumentName: widgetBuilderArg,
};
final Object result = _bindArguments(
context,
node.widget,
arguments,
newWidgetBuilderScope,
stateDepth,
usedWidgets,
);
if (result is Switch) {
return _CurriedSwitch(
FullyQualifiedWidgetName(context.library, ''),
result,
arguments as DynamicMap,
newWidgetBuilderScope,
const <String, Object?>{},
)..propagateSource(result);
}
return result as _CurriedWidget;
};
}
if (node is DynamicMap) {
return node.map<String, Object?>(
(String name, Object? value) => MapEntry<String, Object?>(
name,
_bindArguments(context, value!, arguments, widgetBuilderScope, stateDepth, usedWidgets),
),
);
}
if (node is DynamicList) {
return List<Object>.generate(
node.length,
(int index) => _bindArguments(
context,
node[index]!,
arguments,
widgetBuilderScope,
stateDepth,
usedWidgets,
),
growable: false,
);
}
if (node is Loop) {
final Object input = _bindArguments(context, node.input, arguments, widgetBuilderScope, stateDepth, usedWidgets);
final Object output = _bindArguments(context, node.output, arguments, widgetBuilderScope, stateDepth, usedWidgets);
return Loop(input, output)
..propagateSource(node);
}
if (node is Switch) {
return Switch(
_bindArguments(context, node.input, arguments, widgetBuilderScope, stateDepth, usedWidgets),
node.outputs.map<Object?, Object>(
(Object? key, Object value) {
return MapEntry<Object?, Object>(
key == null ? key : _bindArguments(context, key, arguments, widgetBuilderScope, stateDepth, usedWidgets),
_bindArguments(context, value, arguments, widgetBuilderScope, stateDepth, usedWidgets),
);
},
),
)..propagateSource(node);
}
if (node is ArgsReference) {
return node.bind(arguments)..propagateSource(node);
}
if (node is StateReference) {
return node.bind(stateDepth)..propagateSource(node);
}
if (node is EventHandler) {
return EventHandler(
node.eventName,
_bindArguments(
context,
node.eventArguments,
arguments,
widgetBuilderScope,
stateDepth,
usedWidgets,
) as DynamicMap,
)..propagateSource(node);
}
if (node is SetStateHandler) {
assert(node.stateReference is StateReference);
final BoundStateReference stateReference = (node.stateReference as StateReference).bind(stateDepth);
return SetStateHandler(
stateReference,
_bindArguments(context, node.value, arguments, widgetBuilderScope, stateDepth, usedWidgets),
)..propagateSource(node);
}
assert(node is! WidgetDeclaration);
return node;
}
}
class _ResolvedDynamicList {
const _ResolvedDynamicList(this.rawList, this.result, this.length);
final DynamicList? rawList;
final Object? result;
final int? length;
}
typedef _DataResolverCallback = Object Function(List<Object> dataKey);
typedef _StateResolverCallback = Object Function(List<Object> stateKey, int depth);
typedef _WidgetBuilderArgResolverCallback = Object Function(List<Object> argKey);
abstract class _CurriedWidget extends BlobNode {
const _CurriedWidget(
this.fullName,
this.arguments,
this.widgetBuilderScope,
this.initialState,
);
final FullyQualifiedWidgetName fullName;
final DynamicMap arguments;
final DynamicMap widgetBuilderScope;
final DynamicMap? initialState;
static Object _bindLoopVariable(Object node, Object argument, int depth) {
if (node is DynamicMap) {
return node.map<String, Object?>(
(String name, Object? value) => MapEntry<String, Object?>(name, _bindLoopVariable(value!, argument, depth)),
);
}
if (node is DynamicList) {
return List<Object>.generate(
node.length,
(int index) => _bindLoopVariable(node[index]!, argument, depth),
growable: false,
);
}
if (node is Loop) {
return Loop(_bindLoopVariable(node.input, argument, depth), _bindLoopVariable(node.output, argument, depth + 1))
..propagateSource(node);
}
if (node is Switch) {
return Switch(
_bindLoopVariable(node.input, argument, depth),
node.outputs.map<Object?, Object>(
(Object? key, Object value) => MapEntry<Object?, Object>(
key == null ? null : _bindLoopVariable(key, argument, depth),
_bindLoopVariable(value, argument, depth),
),
)
)..propagateSource(node);
}
if (node is _CurriedLocalWidget) {
return _CurriedLocalWidget(
node.fullName,
node.child,
_bindLoopVariable(node.arguments, argument, depth) as DynamicMap,
_bindLoopVariable(node.widgetBuilderScope, argument, depth) as DynamicMap,
)..propagateSource(node);
}
if (node is _CurriedRemoteWidget) {
return _CurriedRemoteWidget(
node.fullName,
_bindLoopVariable(node.child, argument, depth) as _CurriedWidget,
_bindLoopVariable(node.arguments, argument, depth) as DynamicMap,
_bindLoopVariable(node.widgetBuilderScope, argument, depth) as DynamicMap,
node.initialState,
)..propagateSource(node);
}
if (node is _CurriedSwitch) {
return _CurriedSwitch(
node.fullName,
_bindLoopVariable(node.root, argument, depth) as Switch,
_bindLoopVariable(node.arguments, argument, depth) as DynamicMap,
_bindLoopVariable(node.widgetBuilderScope, argument, depth) as DynamicMap,
node.initialState,
)..propagateSource(node);
}
if (node is LoopReference) {
if (node.loop == depth) {
return node.bind(argument)..propagateSource(node);
}
return node;
}
if (node is BoundArgsReference) {
return BoundArgsReference(_bindLoopVariable(node.arguments, argument, depth), node.parts)
..propagateSource(node);
}
if (node is EventHandler) {
return EventHandler(node.eventName, _bindLoopVariable(node.eventArguments, argument, depth) as DynamicMap)
..propagateSource(node);
}
if (node is SetStateHandler) {
return SetStateHandler(node.stateReference, _bindLoopVariable(node.value, argument, depth))
..propagateSource(node);
}
return node;
}
static _ResolvedDynamicList _listLookup(
DynamicList list,
int targetEffectiveIndex,
_StateResolverCallback stateResolver,
_DataResolverCallback dataResolver,
_WidgetBuilderArgResolverCallback widgetBuilderArgResolver,
) {
int currentIndex = 0;
int effectiveIndex = 0;
while ((effectiveIndex <= targetEffectiveIndex || targetEffectiveIndex < 0) && currentIndex < list.length) {
final Object node = list[currentIndex]!;
if (node is Loop) {
Object inputList = node.input;
while (inputList is! DynamicList) {
if (inputList is BoundArgsReference) {
inputList = _resolveFrom(
inputList.arguments,
inputList.parts,
stateResolver,
dataResolver,
widgetBuilderArgResolver,
);
} else if (inputList is DataReference) {
inputList = dataResolver(inputList.parts);
} else if (inputList is WidgetBuilderArgReference) {
inputList = widgetBuilderArgResolver(
<Object>[inputList.argumentName, ...inputList.parts],
);
} else if (inputList is BoundStateReference) {
inputList = stateResolver(inputList.parts, inputList.depth);
} else if (inputList is BoundLoopReference) {
inputList = _resolveFrom(
inputList.value,
inputList.parts,
stateResolver,
dataResolver,
widgetBuilderArgResolver,
);
} else if (inputList is Switch) {
inputList = _resolveFrom(
inputList,
const <Object>[],
stateResolver,
dataResolver,
widgetBuilderArgResolver,
);
} else {
inputList = DynamicList.empty();
}
assert(inputList is! _ResolvedDynamicList);
}
final _ResolvedDynamicList entry = _listLookup(
inputList,
targetEffectiveIndex >= 0 ? targetEffectiveIndex - effectiveIndex : -1,
stateResolver,
dataResolver,
widgetBuilderArgResolver,
);
if (entry.result != null) {
final Object boundResult = _bindLoopVariable(node.output, entry.result!, 0);
return _ResolvedDynamicList(null, boundResult, null);
}
effectiveIndex += entry.length!;
} else {
if (effectiveIndex == targetEffectiveIndex) {
return _ResolvedDynamicList(null, list[currentIndex], null);
}
effectiveIndex += 1;
}
currentIndex += 1;
}
return _ResolvedDynamicList(list, null, effectiveIndex);
}
static Object _resolveFrom(
Object root,
List<Object> parts,
_StateResolverCallback stateResolver,
_DataResolverCallback dataResolver,
_WidgetBuilderArgResolverCallback widgetBuilderArgResolver,
) {
int index = 0;
Object current = root;
while (true) {
if (current is DataReference) {
if (index < parts.length) {
current = current.constructReference(parts.sublist(index));
index = parts.length;
}
current = dataResolver(current.parts);
continue;
} else if (current is WidgetBuilderArgReference) {
current = widgetBuilderArgResolver(<Object>[current.argumentName, ...current.parts]);
continue;
} else if (current is BoundArgsReference) {
List<Object> nextParts = current.parts;
if (index < parts.length) {
nextParts += parts.sublist(index);
}
parts = nextParts;
current = current.arguments;
index = 0;
continue;
} else if (current is BoundStateReference) {
if (index < parts.length) {
current = current.constructReference(parts.sublist(index));
index = parts.length;
}
current = stateResolver(current.parts, current.depth);
continue;
} else if (current is BoundLoopReference) {
List<Object> nextParts = current.parts;
if (index < parts.length) {
nextParts += parts.sublist(index);
}
parts = nextParts;
current = current.value;
index = 0;
continue;
} else if (current is Switch) {
final Object key = _resolveFrom(
current.input,
const <Object>[],
stateResolver,
dataResolver,
widgetBuilderArgResolver,
);
Object? value = current.outputs[key];
if (value == null) {
value = current.outputs[null];
if (value == null) {
return missing;
}
}
current = value;
continue;
} else if (index >= parts.length) {
if (current is EventHandler) {
current = EventHandler(
current.eventName,
_fix(current.eventArguments, stateResolver, dataResolver, widgetBuilderArgResolver) as DynamicMap,
);
} else if (current is SetStateHandler) {
current = SetStateHandler(
current.stateReference,
_fix(current.value, stateResolver, dataResolver, widgetBuilderArgResolver),
);
}
break;
} else if (current is DynamicMap) {
if (parts[index] is! String) {
return missing;
}
if (!current.containsKey(parts[index])) {
return missing;
}
current = current[parts[index]]!;
} else if (current is DynamicList) {
if (parts[index] is! int) {
return missing;
}
current = _listLookup(
current,
parts[index] as int,
stateResolver,
dataResolver,
widgetBuilderArgResolver,
).result ?? missing;
} else {
assert(current is! ArgsReference);
assert(current is! StateReference);
assert(current is! LoopReference);
return missing;
}
index += 1;
}
assert(current is! Reference, 'Unexpected unbound reference (of type ${current.runtimeType}): $current');
assert(current is! Switch);
assert(current is! Loop);
return current;
}
static Object _fix(
Object root,
_StateResolverCallback stateResolver,
_DataResolverCallback dataResolver,
_WidgetBuilderArgResolverCallback widgetBuilderArgResolver,
) {
if (root is DynamicMap) {
return root.map((String key, Object? value) =>
MapEntry<String, Object?>(
key,
_fix(root[key]!, stateResolver, dataResolver, widgetBuilderArgResolver),
),
);
} else if (root is DynamicList) {
if (root.any((Object? entry) => entry is Loop)) {
final int length = _listLookup(
root,
-1,
stateResolver,
dataResolver,
widgetBuilderArgResolver,
).length!;
return DynamicList.generate(
length,
(int index) => _fix(
_listLookup(root, index, stateResolver, dataResolver, widgetBuilderArgResolver).result!,
stateResolver,
dataResolver,
widgetBuilderArgResolver,
),
);
} else {
return DynamicList.generate(
root.length,
(int index) => _fix(root[index]!, stateResolver, dataResolver, widgetBuilderArgResolver),
);
}
} else if (root is BlobNode) {
return _resolveFrom(root, const <Object>[], stateResolver, dataResolver, widgetBuilderArgResolver);
} else {
return root;
}
}
Object resolve(
List<Object> parts,
_StateResolverCallback stateResolver,
_DataResolverCallback dataResolver,
_WidgetBuilderArgResolverCallback widgetBuilderArgResolver, {
required bool expandLists,
}) {
Object result = _resolveFrom(arguments, parts, stateResolver, dataResolver, widgetBuilderArgResolver);
if (result is DynamicList && expandLists) {
result = _listLookup(result, -1, stateResolver, dataResolver, widgetBuilderArgResolver);
}
assert(result is! Reference);
assert(result is! Switch);
assert(result is! Loop);
return result;
}
Widget build(
BuildContext context,
DynamicContent data,
RemoteEventHandler remoteEventTarget,
List<_WidgetState> states,
) {
return _Widget(
curriedWidget: this,
data: data,
widgetBuilderScope: DynamicContent(widgetBuilderScope),
remoteEventTarget: remoteEventTarget,
states: states,
);
}
Widget buildChild(
BuildContext context,
DataSource source,
DynamicContent data,
RemoteEventHandler remoteEventTarget,
List<_WidgetState> states,
_StateResolverCallback stateResolver,
_DataResolverCallback dataResolver,
_WidgetBuilderArgResolverCallback widgetBuilderArgResolver,
);
@override
String toString() => '$fullName ${initialState ?? "{}"} $arguments';
}
class _CurriedLocalWidget extends _CurriedWidget {
const _CurriedLocalWidget(
FullyQualifiedWidgetName fullName,
this.child,
DynamicMap arguments,
DynamicMap widgetBuilderScope,
) : super(fullName, arguments, widgetBuilderScope, null);
factory _CurriedLocalWidget.error(FullyQualifiedWidgetName fullName, String message) {
return _CurriedLocalWidget(
fullName,
(BuildContext context, DataSource data) => _buildErrorWidget(message),
const <String, Object?>{},
const <String, Object?>{},
);
}
final LocalWidgetBuilder child;
@override
Widget buildChild(
BuildContext context,
DataSource source,
DynamicContent data,
RemoteEventHandler remoteEventTarget,
List<_WidgetState> states,
_StateResolverCallback stateResolver,
_DataResolverCallback dataResolver,
_WidgetBuilderArgResolverCallback widgetBuilderArgResolver,
) {
return child(context, source);
}
}
class _CurriedRemoteWidget extends _CurriedWidget {
const _CurriedRemoteWidget(
FullyQualifiedWidgetName fullName,
this.child,
DynamicMap arguments,
DynamicMap widgetBuilderScope,
DynamicMap? initialState,
) : super(fullName, arguments, widgetBuilderScope, initialState);
final _CurriedWidget child;
@override
Widget buildChild(
BuildContext context,
DataSource source,
DynamicContent data,
RemoteEventHandler remoteEventTarget,
List<_WidgetState> states,
_StateResolverCallback stateResolver,
_DataResolverCallback dataResolver,
_WidgetBuilderArgResolverCallback widgetBuilderArgResolver,
) {
return child.build(context, data, remoteEventTarget, states);
}
@override
String toString() => '${super.toString()} = $child';
}
class _CurriedSwitch extends _CurriedWidget {
const _CurriedSwitch(
FullyQualifiedWidgetName fullName,
this.root,
DynamicMap arguments,
DynamicMap widgetBuilderScope,
DynamicMap? initialState,
) : super(fullName, arguments, widgetBuilderScope, initialState);
final Switch root;
@override
Widget buildChild(
BuildContext context,
DataSource source,
DynamicContent data,
RemoteEventHandler remoteEventTarget,
List<_WidgetState> states,
_StateResolverCallback stateResolver,
_DataResolverCallback dataResolver,
_WidgetBuilderArgResolverCallback widgetBuilderArgResolver,
) {
final Object resolvedWidget = _CurriedWidget._resolveFrom(
root,
const <Object>[],
stateResolver,
dataResolver,
widgetBuilderArgResolver,
);
if (resolvedWidget is _CurriedWidget) {
return resolvedWidget.build(context, data, remoteEventTarget, states);
}
return _buildErrorWidget('Switch in $fullName did not resolve to a widget (got $resolvedWidget).');
}
@override
String toString() => '${super.toString()} = $root';
}
class _Widget extends StatefulWidget {
const _Widget({
required this.curriedWidget,
required this.data,
required this.widgetBuilderScope,
required this.remoteEventTarget,
required this.states,
});
final _CurriedWidget curriedWidget;
final DynamicContent data;
final DynamicContent widgetBuilderScope;
final RemoteEventHandler remoteEventTarget;
final List<_WidgetState> states;
@override
State<_Widget> createState() => _WidgetState();
}
class _WidgetState extends State<_Widget> implements DataSource {
DynamicContent? _state;
DynamicMap? _stateStore;
late List<_WidgetState> _states;
@override
void initState() {
super.initState();
_updateState();
}
@override
void didUpdateWidget(_Widget oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.curriedWidget != widget.curriedWidget) {
_updateState();
}
if (oldWidget.data != widget.data || oldWidget.curriedWidget != widget.curriedWidget || oldWidget.states != widget.states) {
_unsubscribe();
}
}
@override
void dispose() {
_unsubscribe();
super.dispose();
}
void _updateState() {
_stateStore = deepClone(widget.curriedWidget.initialState) as DynamicMap?;
if (_stateStore != null) {
_state ??= DynamicContent();
_state!.updateAll(_stateStore!);
} else {
_state = null;
}
_states = widget.states;
if (_state != null) {
_states = _states.toList()..add(this);
}
}
void _handleSetState(int depth, List<Object> parts, Object value) {
_states[depth].applySetState(parts, value);
}
void applySetState(List<Object> parts, Object value) {
assert(parts.isNotEmpty);
assert(_stateStore != null);
int index = 0;
Object current = _stateStore!;
while (index < parts.length) {
final Object subindex = parts[index];
if (current is DynamicMap) {
if (subindex is! String) {
throw RemoteFlutterWidgetsException('${parts.join(".")} does not identify existing state.');
}
if (!current.containsKey(subindex)) {
throw RemoteFlutterWidgetsException('${parts.join(".")} does not identify existing state.');
}
if (index == parts.length - 1) {
current[subindex] = value;
} else {
current = current[parts[index]]!;
}
} else if (current is DynamicList) {
if (subindex is! int) {
throw RemoteFlutterWidgetsException('${parts.join(".")} does not identify existing state.');
}
if (subindex < 0 || subindex >= current.length) {
throw RemoteFlutterWidgetsException('${parts.join(".")} does not identify existing state.');
}
if (index == parts.length - 1) {
current[subindex] = value;
} else {
current = current[subindex]!;
}
} else {
throw RemoteFlutterWidgetsException('${parts.join(".")} does not identify existing state.');
}
index += 1;
}
_state!.updateAll(_stateStore!);
}
final Map<_Key, _Subscription> _subscriptions = <_Key, _Subscription>{};
void _unsubscribe() {
for (final _Subscription value in _subscriptions.values) {
value.dispose();
}
_subscriptions.clear();
_argsCache.clear();
}
@override
T? v<T extends Object>(List<Object> argsKey) {
assert(T == int || T == double || T == bool || T == String);
final Object value = _fetch(argsKey, expandLists: false);
return value is T ? value : null;
}
@override
bool isList(List<Object> argsKey) {
final Object value = _fetch(argsKey, expandLists: false);
return value is _ResolvedDynamicList
|| value is DynamicList;
}
@override
int length(List<Object> argsKey) {
final Object value = _fetch(argsKey, expandLists: true);
if (value is _ResolvedDynamicList) {
if (value.rawList != null) {
assert(value.length != null);
return value.length!;
}
}
assert(value is! DynamicList);
return 0;
}
@override
bool isMap(List<Object> argsKey) {
final Object value = _fetch(argsKey, expandLists: false);
return value is DynamicMap;
}
@override
Widget child(List<Object> argsKey) {
final Object value = _fetch(argsKey, expandLists: false);
if (value is _CurriedWidget) {
return value.build(context, widget.data, widget.remoteEventTarget, widget.states);
}
return _buildErrorWidget('Not a widget at $argsKey (got $value) for ${widget.curriedWidget.fullName}.');
}
@override
Widget? optionalChild(List<Object> argsKey) {
final Object value = _fetch(argsKey, expandLists: false);
if (value is _CurriedWidget) {
return value.build(context, widget.data, widget.remoteEventTarget, widget.states);
}
return null;
}
@override
List<Widget> childList(List<Object> argsKey) {
final Object value = _fetch(argsKey, expandLists: true);
if (value is _ResolvedDynamicList) {
assert(value.length != null);
final DynamicList fullList = _fetchList(argsKey, value.length!);
return List<Widget>.generate(
fullList.length,
(int index) {
final Object? node = fullList[index];
if (node is _CurriedWidget) {
return node.build(context, widget.data, widget.remoteEventTarget, _states);
}
return _buildErrorWidget('Not a widget at $argsKey (got $node) for ${widget.curriedWidget.fullName}.');
},
);
}
if (value == missing) {
return const <Widget>[];
}
return <Widget>[
_buildErrorWidget('Not a widget list at $argsKey (got $value) for ${widget.curriedWidget.fullName}.'),
];
}
@override
Widget builder(List<Object> argsKey, DynamicMap builderArg) {
return _fetchBuilder(argsKey, builderArg, optional: false)!;
}
@override
Widget? optionalBuilder(List<Object> argsKey, DynamicMap builderArg) {
return _fetchBuilder(argsKey, builderArg);
}
Widget? _fetchBuilder(
List<Object> argsKey,
DynamicMap builderArg, {
bool optional = true,
}) {
final Object value = _fetch(argsKey, expandLists: false);
if (value is _RemoteWidgetBuilder) {
final _CurriedWidget curriedWidget = value(builderArg);
return curriedWidget.build(
context,
widget.data,
widget.remoteEventTarget,
widget.states,
);
}
return optional
? null
: _buildErrorWidget('Not a builder at $argsKey (got $value) for ${widget.curriedWidget.fullName}.');
}
@override
VoidCallback? voidHandler(List<Object> argsKey, [ DynamicMap? extraArguments ]) {
return handler<VoidCallback>(argsKey, (HandlerTrigger callback) => () => callback(extraArguments));
}
@override
T? handler<T extends Function>(List<Object> argsKey, HandlerGenerator<T> generator) {
Object value = _fetch(argsKey, expandLists: true);
if (value is AnyEventHandler) {
value = <Object>[ value ];
} else if (value is _ResolvedDynamicList) {
value = _fetchList(argsKey, value.length!);
}
if (value is DynamicList) {
final List<AnyEventHandler> handlers = value.whereType<AnyEventHandler>().toList();
if (handlers.isNotEmpty) {
return generator(([DynamicMap? extraArguments]) {
for (final AnyEventHandler entry in handlers) {
if (entry is EventHandler) {
DynamicMap arguments = entry.eventArguments;
if (extraArguments != null) {
arguments = DynamicMap.fromEntries(arguments.entries.followedBy(extraArguments.entries));
}
widget.remoteEventTarget(entry.eventName, arguments);
} else if (entry is SetStateHandler) {
assert(entry.stateReference is BoundStateReference);
_handleSetState((entry.stateReference as BoundStateReference).depth, entry.stateReference.parts, entry.value);
}
}
});
}
}
return null;
}
final Map<_Key, Object?> _argsCache = <_Key, Object?>{};
bool _debugFetching = false;
final List<_Subscription> _dependencies = <_Subscription>[];
Object _fetch(List<Object> argsKey, { required bool expandLists }) {
final _Key key = _Key(_kArgsSection, argsKey);
final Object? value = _argsCache[key];
if (value != null && (value is! DynamicList || !expandLists)) {
return value;
}
assert(!_debugFetching);
try {
_debugFetching = true;
final Object result = widget.curriedWidget.resolve(
argsKey,
_stateResolver,
_dataResolver,
_widgetBuilderArgResolver,
expandLists: expandLists,
);
for (final _Subscription subscription in _dependencies) {
subscription.addClient(key);
}
_argsCache[key] = result;
return result;
} finally {
_dependencies.clear();
_debugFetching = false;
}
}
DynamicList _fetchList(List<Object> argsKey, int length) {
return DynamicList.generate(length, (int index) {
return _fetch(<Object>[...argsKey, index], expandLists: false);
});
}
Object _dataResolver(List<Object> rawDataKey) {
final _Key dataKey = _Key(_kDataSection, rawDataKey);
final _Subscription subscription;
if (!_subscriptions.containsKey(dataKey)) {
subscription = _Subscription(widget.data, this, rawDataKey);
_subscriptions[dataKey] = subscription;
} else {
subscription = _subscriptions[dataKey]!;
}
_dependencies.add(subscription);
return subscription.value;
}
Object _widgetBuilderArgResolver(List<Object> rawDataKey) {
final _Key widgetBuilderArgKey = _Key(_kWidgetBuilderArgSection, rawDataKey);
final _Subscription subscription = _subscriptions[widgetBuilderArgKey] ??= _Subscription(
widget.widgetBuilderScope,
this,
rawDataKey,
);
_dependencies.add(subscription);
return subscription.value;
}
Object _stateResolver(List<Object> rawStateKey, int depth) {
final _Key stateKey = _Key(depth, rawStateKey);
final _Subscription subscription;
if (!_subscriptions.containsKey(stateKey)) {
if (depth >= _states.length) {
throw const RemoteFlutterWidgetsException('Reference to state value did not correspond to any stateful remote widget.');
}
final DynamicContent? state = _states[depth]._state;
if (state == null) {
return missing;
}
subscription = _Subscription(state, this, rawStateKey);
_subscriptions[stateKey] = subscription;
} else {
subscription = _subscriptions[stateKey]!;
}
_dependencies.add(subscription);
return subscription.value;
}
void updateData(Set<_Key> affectedArgs) {
setState(() {
for (final _Key key in affectedArgs) {
_argsCache[key] = null;
}
});
}
@override
Widget build(BuildContext context) {
return widget.curriedWidget.buildChild(
context,
this,
widget.data,
widget.remoteEventTarget,
_states,
_stateResolver,
_dataResolver,
_widgetBuilderArgResolver,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(StringProperty('name', '${widget.curriedWidget.fullName}'));
}
}
const int _kDataSection = -1;
const int _kArgsSection = -2;
const int _kWidgetBuilderArgSection = -3;
@immutable
class _Key {
_Key(this.section, this.parts) : assert(_isValidKey(parts), '$parts is not a valid key');
static bool _isValidKey(List<Object> parts) {
return parts.every((Object segment) => segment is int || segment is String);
}
final int section;
final List<Object> parts;
@override
bool operator ==(Object other) {
return other is _Key
&& section == other.section
&& listEquals(parts, other.parts);
}
@override
int get hashCode => Object.hash(section, Object.hashAll(parts));
}
class _Subscription {
_Subscription(this._data, this._state, this._dataKey) {
_update(_data.subscribe(_dataKey, _update));
}
final DynamicContent _data;
final _WidgetState _state;
final List<Object> _dataKey;
final Set<_Key> _clients = <_Key>{};
Object get value => _value;
late Object _value;
void _update(Object value) {
_state.updateData(_clients);
_value = value;
}
void addClient(_Key key) {
_clients.add(key);
}
void dispose() {
_data.unsubscribe(_dataKey, _update);
}
}
Widget _buildErrorWidget(String message) {
final FlutterErrorDetails detail = FlutterErrorDetails(
exception: message,
stack: StackTrace.current,
library: 'Remote Flutter Widgets',
);
FlutterError.reportError(detail);
return ErrorWidget.builder(detail);
}