Skip to content

Wrappers

Ethan edited this page Feb 12, 2022 · 7 revisions

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:

  1. When calling build in bridgeGet, a wrapper ($Widget) is used to give the Eval environment access to the resulting Widget object that Text will natively produce if we don't override its build method in an Eval subclass.
  2. When overriding build for native Dart use, we wrap the BuildContext 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, 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);
}

Hot wrappers

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.

Clone this wiki locally