-
Notifications
You must be signed in to change notification settings - Fork 40
Wrappers
Wrappers allow Eval to use instances created outside of the Eval environment. This is necessary when calling from Eval into a native Dart function that returns a value, and can be used when passing an argument into an Eval function.
For example, a Flutter Text
bridge class may look something like this (abbreviated for clarity):
class $Text$bridge extends Text with $Bridge {
@override
$Value? $bridgeGet(String identifier) {
switch (identifier) {
case 'build':
return $Function((rt, target, args) {
return $Widget.wrap(super.build(args[0].$value));
});
}
throw UnimplementedError();
}
Widget? build(BuildContext context) =>
$_invoke('build', [$BuildContext.wrap(context)]);
}
Here we are using wrappers in two places:
- When calling
build
inbridgeGet
, a wrapper ($Widget
) is used to give the Eval environment access to the resultingWidget
object thatText
will natively produce if we don't override its build method in an Eval subclass. - When overriding
build
for native Dart use, we wrap theBuildContext
argument with$BuildContext
, so that the Eval environment understands the arguments if we do override its build method in an Eval subclass.
A wrapper for Text
itself, on the other hand, looks something like this:
class $Text implements Text, $Instance {
$Text.wrap(this.$value);
@override
final Text $value;
@override
$Value? $getProperty(Runtime runtime, String identifier) {
switch(identifier) {
case 'build':
return $Function(
(rt, target, args) => $Widget.wrap((target.$value as Text).build(args[0].$value)));
}
}
Widget build(BuildContext context) => $value.build(context);
@override
int $getRuntimeType(Runtime runtime) => runtime.lookupType(FlutterTypes.text);
}
The Text wrapper shown above is also a bimodal wrapper since it implements both the $Instance
and Text
interfaces. Wrappers don't have to be bimodal, and non-bimodal wrappers that implement only $Instance
may be significantly easier to write. Bimodal wrappers are only required when specifying a wrapped type as a generic type parameter. Why is this? Well, let's take a look at the following class:
class State<T extends Widget> {
State(this.widget);
final T widget;
}
and a naive bridge class for this widget (abridged for clarity):
class $State$bridge<T extends Widget> extends State<T> with $Bridge<State<T>> {
static const $type = ...;
static const $declaration = ...;
@override
$Value? $bridgeGet(String identifier) {
switch (identifier) {
case 'widget':
return $Widget.wrap(widget); // << What do we do here?
}
}
@override
T get widget => $_get('widget');
}
You can see this class has a problem. When we want to retrieve the widget parameter as a $Value
, we don't know what its exact type will be, only that it must extend Widget
, so that's the most specific type we can wrap it in - not very useful.
With the use of bimodal wrappers, we can amend the function to this:
...
@override
$Value? $bridgeGet(String identifier) {
switch (identifier) {
case 'widget':
if (widget is $Instance) {
return widget as $Instance;
}
return $Widget.wrap(widget);
}
}
...
This works only if the class we specify as the T type parameter implements both its original type (allowing it to be used as a Widget
and stored in the widget
field) and $Instance
, allowing it to be returned from $bridgeGet
.