-
Notifications
You must be signed in to change notification settings - Fork 56
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
EvalValue? $bridgeGet(String identifier) {
switch (identifier) {
case 'build':
return EvalFunctionImpl((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
buildinbridgeGet, a wrapper ($Widget) is used to give the Eval environment access to the resultingWidgetobject thatTextwill natively produce if we don't override its build method in an Eval subclass. - When overriding
buildfor native Dart use, we wrap theBuildContextargument 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, EvalInstance {
$Text(String id, Text value) : $value = runtimeOverride(id) as Text? ?? value;
$Text.wrap(this.$value);
@override
final Text $value;
@override
EvalValue? $getProperty(Runtime runtime, String identifier) {
switch(identifier) {
case 'build':
return EvalFunctionImpl(
(rt, target, args) => $Widget.wrap((target.$value as Text).build(args[0].$value)));
}
}
Widget build(BuildContext context) => $value.build(context);
}Notice the first constructor with the runtimeOverride line? That's because the above class is also a hot wrapper. A hot wrapper is a class that makes it easy for Eval to dynamically substitute a component with an Eval equivalent at runtime, such as when using code push. For example, let's say you define your app like so:
class MyApp extends StatelessWidget {
MyApp();
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
// Use a hot wrapper for the app bar
appBar: $AppBar('#myapp-build-appbar-1', title: const Text('My App!')),
// Use a hot wrapper for the app content
body: $Column('#myapp-build-column-1', children: const<Widget>[
_buildMainScreen(),
_buildExtraContent()
]);
);
);
}
}Here we are using standard Dart code for all of the content of the app up until the Column and AppBar, which we replace with hot wrapper $Column and $AppBar instances and specify globally unique IDs to identify them (eg '#myapp-build-column-1'). Now, if we want to push some new code to our app, we will be able to easily override any code inside of the Column or AppBar constructor without affecting anything else or each other. For example, if we simply want to change the title in the AppBar, we can push a piece of code that will induce dart_eval to use a bridge call for the content of the app bar, but the Column will remain native Dart code. This is highly beneficial to performance, since most of the app's code can continue to run in native, compiled Dart while only the piecies you've changed will be run in the slower dart_eval VM.