flutter_eval

repository·master·Indexed 19 days ago

https://github.com/ethanblake4/flutter_eval

A Flutter bridge for dart_eval that enables code-push, dynamic widgets, and runtime evaluation of Flutter code. It allows developers to implement over-the-air (OTA) updates via HotSwapLoader and HotSwap widgets, load UI from a server using EvalWidget, or evaluate user-inputted code with CompilerWidget and RuntimeWidget using a custom bytecode interpreter.

Tokens
7.6K
Snippets
31
Records
36
Agent score
63%

What's inside flutter_eval

  1. Use EvalWidget for Dynamic UI and Server-Driven UI

    master

    The EvalWidget is a versatile widget that automatically switches behavior based on the build mode:

    • Debug Mode: It dynamically compiles the provided Dart code into EVC bytecode, saves it to the assetPath, and runs it. This allows for hot-reloading during development.
    • Release Mode: It ignores the provided Dart code and attempts to load EVC bytecode from the assetPath or a network uri.

    Use EvalWidget when you want a seamless transition from local development (writing Dart) to production (loading pre-compiled EVC).

    return EvalWidget(
        packages: {
          'example': {
            'main.dart': '''
      import 'package:flutter/material.dart';
      class MyWidget extends StatelessWidget {
        MyWidget(this.name);
        final String name;
        @override
        Widget build(BuildContext context) => Text(name);
      }
    ''',
          },
        },
        assetPath: 'assets/program.evc',
        library: 'package:example/main.dart',
        function: 'MyWidget.',
        args: [$String('Example name')]
    );
  2. Configure security and permissions

    master

    By default, flutter_eval uses a secure execution model that restricts access to the filesystem, network, and sensitive APIs.

    To grant access to a MethodChannel or other restricted APIs, you must explicitly add a MethodChannelPermission to the permissions parameter of the widget you are using (EvalWidget, CompilerWidget, or RuntimeWidget).

  3. Compare EvalWidget, CompilerWidget, and RuntimeWidget

    master

    Depending on your use case, choose one of these three helper widgets:

    WidgetBehaviorBest Use Case
    EvalWidgetCompiles Dart in debug; loads EVC in release.Standard dynamic UI/Server-driven UI.
    CompilerWidgetAlways compiles and runs provided Dart code.Calculators, 'learn to code' apps, or user-scriptable tools.
    RuntimeWidgetAlways loads EVC bytecode; does not accept Dart code.High-performance apps where code is pre-compiled via CLI.

    Note: CompilerWidget and EvalWidget are slower in debug mode due to compilation, but CompilerWidget is always slower because it never uses pre-compiled bytecode.

  4. Implement Code Push with HotSwapLoader and HotSwap

    master

    To enable over-the-air updates (code-push), follow these steps:

    1. Setup the Loader: Wrap your application (e.g., MaterialApp) in a HotSwapLoader widget and provide a uri pointing to your hosted .evc update file.
    2. Identify Hot-swappable areas: Place HotSwap widgets at the specific locations in your widget tree that you want to update dynamically. Each HotSwap requires a unique id (prefixed with #) and an args list. Use $BuildContext.wrap(context) to pass the current context.
    3. Update Strategies: HotSwap widgets support three loading strategies:
      • immediate: Loads the update immediately (default in debug/profile mode).
      • cache: Uses a cached version.
      • cacheApplyOnRestart: Applies the cached update upon the next app restart (default in release mode).
    4. Loading State: You can provide a placeholder widget via the loading parameter to display while the update is being fetched from the cache.
    // 1. Setup HotSwapLoader at the root
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return HotSwapLoader(
            uri: 'https://mysite.com/app_update/version_xxx.evc',
            child: MaterialApp(
              ...
            ),
        );
      }
    }
    
    // 2. Use HotSwap at specific locations
    class MyHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return HotSwap(
          id: '#myHomePage',
          args: [$BuildContext.wrap(context)],
          childBuilder: (context) => Scaffold(
            ...
          ),
        );
      }
    }
  5. Compile and execute Dart code during development

    master

    For development, you can run both the compilation and execution steps within your app to receive immediate feedback on code errors. This involves using the Compiler class to transform Dart source into EVC bytecode and the Runtime class to execute it.

    1. Initialize a Compiler and add the flutterEvalPlugin.
    2. Call compiler.compile() with a map containing your package and source code.
    3. Write the resulting bytecode to a file using program.write().
    4. Initialize a Runtime using Runtime.ofProgram(program) and add the flutterEvalPlugin.
    class ExampleState extends State<Example> {
      late Runtime runtime;
    
      @override
      void initState() {
        super.initState();
    
        final compiler = Compiler();
        compiler.addPlugin(flutterEvalPlugin);
    
        final program = compiler.compile({
          'example': { 'main.dart': '''
              import 'package:flutter/material.dart';
              
              class HomePage extends StatelessWidget {
                HomePage(this.number);
                final int number;
                
                @override
                Widget build(BuildContext context) {
                  return Padding(
                    padding: EdgeInsets.all(2.3 * 5),
                    child: Container(
                      color: Colors.green,
                      child: Text('Current amount: ' + number.toString())
                    )
                  );
                }
              }
            ''' }
        });
    
        final file = File('out.evc');
        file.writeAsBytesSync(program.write());
        
        runtime = Runtime.ofProgram(program);
        runtime.addPlugin(flutterEvalPlugin);
      }
    
      @override
      Widget build(BuildContext context) {
        return (runtime.executeLib('package:example/main.dart', 'HomePage.', [$int(55)]) as $Value).$value;
      }
    }
  6. Compile Hot Update Packages with dart_eval CLI

    master

    To create the .evc files used for code-push, follow this workflow:

    1. Install the CLI:
      dart pub global activate dart_eval
    2. Prepare the Hot Update Package:
      • Create a new Flutter package (flutter create --template=package).
      • Add eval_annotation to the package's pubspec.yaml.
      • Download the flutter_eval.json binding file from the flutter_eval GitHub Releases page (matching your current flutter_eval version).
      • Place the JSON file in .dart_eval/bindings/flutter_eval.json within your hot update package.
    3. Annotate Updates: In your hot update package, create top-level functions for each HotSwap widget and annotate them with @RuntimeOverride('#your_id').
    4. Compile: Run the following command in the root of your hot update package:
      dart_eval compile -o version_xxx.evc
      The resulting .evc file can then be uploaded to your server.
    dart pub global activate dart_eval
    dart_eval compile -o version_xxx.evc
  7. Generate EVC update files for code push

    master

    To perform a hot update in the flutter_eval code push sample app, you must create an EVC (Eval Compiled) update file. This is done by taking a Flutter package containing dart_eval JSON bindings and compiling it using the dart_eval CLI. The resulting EVC file is then consumed by the HotSwapLoader within the host application to update the running code dynamically.

    # Conceptual workflow:
    # 1. Create a Flutter package with dart_eval JSON bindings
    # 2. Use dart_eval CLI to compile the package into an EVC file
    # 3. Provide the EVC file to the HotSwapLoader in the host app
  8. Execute pre-compiled EVC bytecode in production

    master

    In production, you should avoid running the Compiler at runtime as it is slow. Instead, use the pre-compiled EVC bytecode (.evc file) generated during development. EVC bytecode is platform-agnostic; you can generate it on Flutter Desktop and use it in a Flutter Mobile app.

    To use pre-compiled bytecode:

    1. Include the .evc file in your app's assets.
    2. Load the bytecode using rootBundle.load().
    3. Initialize the Runtime with Runtime(ByteData.sublistView(bytecode)).
    4. Add the flutterEvalPlugin to the runtime.

    You can also load the bytecode over a network. Since EVC bytecode compresses well (approx. 4x ratio), using gzip compression for network transfers is recommended.

    import 'package:flutter/services.dart' show rootBundle;
    
    class ExampleState extends State<Example> {
      Runtime? runtime;
    
      @override
      void initState() {
        super.initState();
        
        rootBundle.load('assets/out.evc').then((bytecode) => setState(() {
          runtime = Runtime(ByteData.sublistView(bytecode));
          runtime.addPlugin(flutterEvalPlugin);
        }));
      }
    
      @override
      Widget build(BuildContext context) {
        if (runtime == null) return CircularProgressIndicator();
        return (runtime.executeLib('package:example/main.dart', 'HomePage.', [$int(55)]) as $Value).$value;
      }
    }
  9. Customize the iOS launch screen assets

    master

    To change the launch screen image for the iOS version of the code_push_app example, you can use either of the following methods:

    1. Direct File Replacement: Replace the existing image files located in the ios/Runner/Assets.xcassets/LaunchImage.imageset/ directory with your own assets.
    2. Xcode Interface:
      • Open the iOS project in Xcode by running open ios/Runner.xcworkspace from your terminal.
      • In the Xcode Project Navigator, navigate to Runner/Assets.xcassets.
      • Drag and drop your desired images into the asset catalog.
    open ios/Runner.xcworkspace
  10. Customize iOS launch screen assets

    master

    To change the launch screen image for the iOS version of your app, you can use one of two methods:

    1. Direct File Replacement: Replace the existing image files directly within the example/ios/Runner/Assets.xcassets/LaunchImage.imageset/ directory with your own assets.
    2. Xcode Interface:
      • Open the iOS project in Xcode by running open ios/Runner.xcworkspace from your terminal.
      • In the Xcode Project Navigator, navigate to Runner/Assets.xcassets.
      • Drag and drop your desired images into the asset catalog to replace the launch images.
    open ios/Runner.xcworkspace
  11. Access Material Design colors in evaluated code

    master

    The flutter_eval environment provides access to Material Design color swatches and accent colors. You can use these colors to style widgets within your dynamically evaluated code. Colors are available as MaterialColor (for primary swatches) or MaterialAccentColor (for accent swatches), allowing you to access specific shades using index notation (e.g., Colors.green[400]).

    Icon(
      Icons.widgets,
      color: Colors.green[400],
    )