├── assets
└── idk.none
├── plugins
├── models.dart
├── assets
│ ├── metadata.json
│ ├── workplace.jpg
│ └── main.dart
├── functions
│ ├── metadata.json
│ └── main.dart
├── static
│ ├── metadata.json
│ └── main.dart
├── widgets
│ ├── metadata.json
│ └── main.dart
├── pubspec.yaml
├── package_plugins.dart
├── plugin_base.json
└── pubspec.lock
├── benchmark
├── README.md
├── pubspec.yaml
├── benchmark
│ ├── main_benchmark.dart
│ └── counter.dart
└── pubspec.lock
├── android
├── app
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── drawable
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable-v21
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── values
│ │ │ │ │ └── styles.xml
│ │ │ │ └── values-night
│ │ │ │ │ └── styles.xml
│ │ │ ├── kotlin
│ │ │ │ └── ilya
│ │ │ │ │ └── zverev
│ │ │ │ │ └── info
│ │ │ │ │ └── extension_test
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── profile
│ │ │ └── AndroidManifest.xml
│ └── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── .gitignore
├── build.gradle
└── settings.gradle
├── tool
├── test_plugin.sh
├── generate_bindings.dart
└── test_plugin.dart
├── LICENSE
├── README.md
├── pubspec.yaml
├── .gitignore
├── .metadata
├── lib
├── models.dart
├── plugins.dart
├── main.dart
├── plugin_provider.dart
└── bridges.dart
├── analysis_options.yaml
└── pubspec.lock
/assets/idk.none:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/plugins/models.dart:
--------------------------------------------------------------------------------
1 | ../lib/models.dart
--------------------------------------------------------------------------------
/benchmark/README.md:
--------------------------------------------------------------------------------
1 | # Dart Eval Quick Benchmark
2 |
3 | Run it as `dart run benchmark`.
4 |
--------------------------------------------------------------------------------
/plugins/assets/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "assets",
3 | "name": "Nice photo",
4 | "version": 1
5 | }
6 |
--------------------------------------------------------------------------------
/plugins/assets/workplace.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zverik/flutter-plugins-test/main/plugins/assets/workplace.jpg
--------------------------------------------------------------------------------
/plugins/functions/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "functions",
3 | "name": "Add Minus Button",
4 | "version": 1
5 | }
6 |
--------------------------------------------------------------------------------
/plugins/static/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "static",
3 | "name": "Start from 9, increment by 2",
4 | "version": 1
5 | }
6 |
--------------------------------------------------------------------------------
/plugins/widgets/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "widgets",
3 | "name": "Big number and persistent counter",
4 | "version": 1
5 | }
6 |
--------------------------------------------------------------------------------
/plugins/static/main.dart:
--------------------------------------------------------------------------------
1 | import '../models.dart';
2 |
3 | PluginBase setup(PluginContext context) => PluginBase(context, initial: 9, step: 2);
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zverik/flutter-plugins-test/main/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zverik/flutter-plugins-test/main/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zverik/flutter-plugins-test/main/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zverik/flutter-plugins-test/main/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zverik/flutter-plugins-test/main/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/ilya/zverev/info/extension_test/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package ilya.zverev.info.extension_test
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity()
6 |
--------------------------------------------------------------------------------
/benchmark/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: dart_eval_bench
2 | description: 'Dart Eval Benchmark'
3 | publish_to: 'none'
4 |
5 | version: 1.0.0+1
6 |
7 | environment:
8 | sdk: ^3.6.0
9 |
10 | dependencies:
11 | dart_eval: ^0.7.10
12 | benchmark: ^0.3.0
13 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
6 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/to/reference-keystore
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | }
7 |
8 | rootProject.buildDir = "../build"
9 | subprojects {
10 | project.buildDir = "${rootProject.buildDir}/${project.name}"
11 | }
12 | subprojects {
13 | project.evaluationDependsOn(":app")
14 | }
15 |
16 | tasks.register("clean", Delete) {
17 | delete rootProject.buildDir
18 | }
19 |
--------------------------------------------------------------------------------
/tool/test_plugin.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e -u
3 | if [ $# -lt 1 ]; then
4 | echo Usage: $0 [c] plugin_path
5 | exit 1
6 | fi
7 |
8 | PROJ_DIR="$(cd "$(dirname "$0")/.." > /dev/null; pwd -P)"
9 |
10 | if [ "$1" == "c" ]; then
11 | echo Compiling...
12 | cd "$PROJ_DIR/plugins"
13 | dart package_plugins.dart
14 | shift
15 | fi
16 |
17 | cd "$PROJ_DIR"
18 | flutter test tool/test_plugin.dart --dart-define "PLUGIN=$1"
19 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/plugins/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: package_plugins
2 | description: 'Plugin Packager'
3 | publish_to: 'none'
4 |
5 | version: 1.0.0+1
6 |
7 | environment:
8 | sdk: ^3.6.0
9 |
10 | dependencies:
11 | dart_eval: ^0.7.10
12 | flutter_eval: ^0.7.6
13 | archive: ^4.0.2
14 | path: ^1.9.1
15 |
16 | dependency_overrides:
17 | flutter_eval:
18 | git:
19 | url: https://github.com/Zverik/flutter_eval.git
20 | ref: flutter_3_27
21 |
22 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2 | Version 2, December 2004
3 |
4 | Copyright (C) 2004 Sam Hocevar
5 |
6 | Everyone is permitted to copy and distribute verbatim or modified
7 | copies of this license document, and changing it is allowed as long
8 | as the name is changed.
9 |
10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12 |
13 | 0. You just DO WHAT THE FUCK YOU WANT TO.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flutter Plugin Test
2 |
3 | This is an experiment in creating a plugin infrastructure around the
4 | Flutter demo app.
5 |
6 | A plugin is a zip archive, which must contain at least a `metadata.json`
7 | file with `id` and `name` fields, and a code compiled to `plugin.evc`.
8 | See the `plugins` directory for examples and for a build script.
9 | Open a plugin with the app to install it.
10 |
11 | To compile and test plugins, run `tool/test_plugin.sh c plugins/`
12 |
13 | ## Author and License
14 |
15 | Written by Ilya Zverev, published under the WTFPL.
16 |
--------------------------------------------------------------------------------
/tool/generate_bindings.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:io';
3 |
4 | import 'package:dart_eval/dart_eval_bridge.dart';
5 | import 'package:extension_test/bridges.dart';
6 |
7 | void main() {
8 | // To properly generate the bindings, dart_eval needs Flutter context which is
9 | // delivered when running Dart programs via `flutter test`,
10 | // but isn't for Dart code ran via `dart run`.
11 | final serializer = BridgeSerializer();
12 | serializer.addPlugin(const PluginBasePlugin());
13 | final output = serializer.serialize();
14 | File('plugins/plugin_base.json').writeAsStringSync(json.encode(output));
15 | }
16 |
--------------------------------------------------------------------------------
/plugins/assets/main.dart:
--------------------------------------------------------------------------------
1 | import '../models.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class AssetPlugin extends PluginBase {
5 | AssetPlugin(PluginContext context) : super(context);
6 |
7 | @override
8 | Widget? numberWidget(BuildContext context, AppState state) {
9 | return Stack(children: [
10 | Image.file(
11 | this.context.getFile('workplace.jpg'),
12 | fit: BoxFit.fill,
13 | ),
14 | Padding(
15 | padding: const EdgeInsets.only(left: 20),
16 | child: Text(
17 | state.counter.toString(),
18 | style: TextStyle(color: Colors.blue, fontSize: 100),
19 | ),
20 | ),
21 | ]);
22 | }
23 | }
24 |
25 | PluginBase setup(PluginContext context) => AssetPlugin(context);
26 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }()
9 |
10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
11 |
12 | repositories {
13 | google()
14 | mavenCentral()
15 | gradlePluginPortal()
16 | }
17 | }
18 |
19 | plugins {
20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
21 | id "com.android.application" version "8.3.2" apply false
22 | id "org.jetbrains.kotlin.android" version "2.0.20" apply false
23 | }
24 |
25 | include ":app"
26 |
--------------------------------------------------------------------------------
/plugins/functions/main.dart:
--------------------------------------------------------------------------------
1 | import '../models.dart';
2 | import 'dart:math';
3 |
4 | class FunctionPlugin extends PluginBase {
5 | final String _kMinusButton = '-1';
6 | final String _kResetButton = 'X';
7 | final Random _random = Random();
8 |
9 | FunctionPlugin(PluginContext context) : super(context) {
10 | // buttons.add(_kMinusButton);
11 | }
12 |
13 | @override
14 | List get buttons => [_kResetButton, _kMinusButton];
15 |
16 | @override
17 | int get step => _random.nextInt(5);
18 |
19 | @override
20 | void onButtonTapped(String button, AppState state) {
21 | if (button == _kMinusButton) {
22 | state.counter -= 1;
23 | } else if (button == _kResetButton) {
24 | state.counter = state.initial;
25 | }
26 | }
27 | }
28 |
29 | PluginBase setup(PluginContext context) => FunctionPlugin(context);
30 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: extension_test
2 | description: "Testing eval and Lua for plugins"
3 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
4 |
5 | version: 1.0.0+1
6 |
7 | environment:
8 | sdk: ^3.6.0
9 |
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 | receive_sharing_intent:
14 | git:
15 | url: https://github.com/KasemJaffer/receive_sharing_intent.git
16 | flutter_archive: ^6.0.3
17 | path_provider: ^2.1.5
18 | shared_preferences: ^2.3.4
19 | flutter_eval: ^0.7.6
20 | dart_eval: ^0.7.10
21 |
22 | dependency_overrides:
23 | flutter_eval:
24 | git:
25 | url: https://github.com/Zverik/flutter_eval.git
26 | ref: flutter_3_27
27 |
28 | dev_dependencies:
29 | flutter_test:
30 | sdk: flutter
31 | flutter_lints: ^5.0.0
32 |
33 | flutter:
34 | uses-material-design: true
35 | assets:
36 | - assets/
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.zip
2 | plugins/*/*.evc
3 | plugins/test_eval/
4 |
5 | # Miscellaneous
6 | *.class
7 | *.log
8 | *.pyc
9 | *.swp
10 | .DS_Store
11 | .atom/
12 | .build/
13 | .buildlog/
14 | .history
15 | .svn/
16 | .swiftpm/
17 | migrate_working_dir/
18 |
19 | # IntelliJ related
20 | *.iml
21 | *.ipr
22 | *.iws
23 | .idea/
24 |
25 | # The .vscode folder contains launch configuration and tasks you configure in
26 | # VS Code which you may wish to be included in version control, so this line
27 | # is commented out by default.
28 | #.vscode/
29 |
30 | # Flutter/Dart/Pub related
31 | **/doc/api/
32 | **/ios/Flutter/.last_build_id
33 | .dart_tool/
34 | .flutter-plugins
35 | .flutter-plugins-dependencies
36 | .pub-cache/
37 | .pub/
38 | /build/
39 |
40 | # Symbolication related
41 | app.*.symbols
42 |
43 | # Obfuscation related
44 | app.*.map.json
45 |
46 | # Android Studio will place build artifacts here
47 | /android/app/debug
48 | /android/app/profile
49 | /android/app/release
50 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "17025dd88227cd9532c33fa78f5250d548d87e9a"
8 | channel: "stable"
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
17 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
18 | - platform: android
19 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
20 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
21 | - platform: ios
22 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
23 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
24 |
25 | # User provided section
26 |
27 | # List of Local paths (relative to this file) that should be
28 | # ignored by the migrate tool.
29 | #
30 | # Files that are not part of the templates will be ignored by default.
31 | unmanaged_files:
32 | - 'lib/main.dart'
33 | - 'ios/Runner.xcodeproj/project.pbxproj'
34 |
--------------------------------------------------------------------------------
/lib/models.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | /// This class has all the values for the state.
6 | /// The main one is [counter], which replaces the old
7 | /// state variable from the demo app.
8 | class AppState extends ChangeNotifier {
9 | int initial = 0;
10 | int step = 1;
11 | int _counter = 0;
12 |
13 | int get counter => _counter;
14 |
15 | set counter(int value) {
16 | _counter = value;
17 | notifyListeners();
18 | }
19 |
20 | @override
21 | String toString() => 'AppState($_counter, ($initial, $step))';
22 | }
23 |
24 | abstract class PluginContext implements Listenable {
25 | final AppState state;
26 | PluginContext(this.state);
27 |
28 | Future readPreference(String key);
29 | Future savePreference(String key, int value);
30 | void repaint();
31 | File getFile(String name);
32 | }
33 |
34 | class PluginBase {
35 | int initial;
36 | int step;
37 | final PluginContext context;
38 | List buttons = [];
39 |
40 | PluginBase(this.context, {this.initial = 0, this.step = 1});
41 |
42 | dynamic init() {}
43 |
44 | void onButtonTapped(String button, AppState state) {}
45 |
46 | void onCounterChanged(AppState state) {}
47 |
48 | Widget? numberWidget(BuildContext context, AppState state) {
49 | return null;
50 | }
51 |
52 | Widget? settingsWidget(BuildContext context) {
53 | return null;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at https://dart.dev/lints.
17 | #
18 | # Instead of disabling a lint rule for the entire project in the
19 | # section below, it can also be suppressed for a single line of code
20 | # or a specific dart file by using the `// ignore: name_of_lint` and
21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
22 | # producing the lint.
23 | rules:
24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
26 |
27 | # Additional information about this file can be found at
28 | # https://dart.dev/guides/language/analysis-options
29 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
5 | id "dev.flutter.flutter-gradle-plugin"
6 | }
7 |
8 | android {
9 | namespace = "ilya.zverev.info.extension_test"
10 | compileSdk = flutter.compileSdkVersion
11 | ndkVersion = flutter.ndkVersion
12 |
13 | compileOptions {
14 | sourceCompatibility = JavaVersion.VERSION_1_8
15 | targetCompatibility = JavaVersion.VERSION_1_8
16 | }
17 |
18 | kotlinOptions {
19 | jvmTarget = JavaVersion.VERSION_1_8
20 | }
21 |
22 | defaultConfig {
23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
24 | applicationId = "ilya.zverev.info.extension_test"
25 | // You can update the following values to match your application needs.
26 | // For more information, see: https://flutter.dev/to/review-gradle-config.
27 | minSdk = flutter.minSdkVersion
28 | targetSdk = flutter.targetSdkVersion
29 | versionCode = flutter.versionCode
30 | versionName = flutter.versionName
31 | }
32 |
33 | buildTypes {
34 | release {
35 | // TODO: Add your own signing config for the release build.
36 | // Signing with the debug keys for now, so `flutter run --release` works.
37 | signingConfig = signingConfigs.debug
38 | }
39 | }
40 | }
41 |
42 | flutter {
43 | source = "../.."
44 | }
45 |
--------------------------------------------------------------------------------
/plugins/widgets/main.dart:
--------------------------------------------------------------------------------
1 | import '../models.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class WidgetPlugin extends PluginBase {
5 | bool needSave = true;
6 | bool readValue = false;
7 |
8 | WidgetPlugin(PluginContext context) : super(context);
9 |
10 | @override
11 | Future init() async {
12 | final needSaveInt = await context.readPreference('widget-save');
13 | needSave = needSaveInt == 1;
14 | if (needSave) {
15 | final int? value = await context.readPreference('widget-counter');
16 | if (value != null) context.state.counter = value;
17 | }
18 | readValue = true;
19 | }
20 |
21 | @override
22 | void onCounterChanged(AppState state) {
23 | if (needSave && readValue) {
24 | context.savePreference('widget-counter', state.counter);
25 | }
26 | }
27 |
28 | @override
29 | Widget? numberWidget(BuildContext context, AppState state) {
30 | return Text(
31 | state.counter.toString(),
32 | style: TextStyle(
33 | color: Colors.red,
34 | fontSize: 300,
35 | fontWeight: FontWeight.bold,
36 | ),
37 | );
38 | }
39 |
40 | @override
41 | Widget? settingsWidget(BuildContext context) {
42 | return Column(
43 | children: [
44 | SwitchListTile(
45 | title: Text('Persist counter'),
46 | value: needSave,
47 | onChanged: (value) {
48 | needSave = value;
49 | this.context.savePreference('widget-save', needSave ? 1 : 0);
50 | if (needSave) {
51 | onCounterChanged(this.context.state);
52 | }
53 | this.context.repaint();
54 | },
55 | ),
56 | ],
57 | );
58 | }
59 | }
60 |
61 | PluginBase setup(PluginContext context) => WidgetPlugin(context);
62 |
--------------------------------------------------------------------------------
/benchmark/benchmark/main_benchmark.dart:
--------------------------------------------------------------------------------
1 | import 'package:dart_eval/dart_eval.dart';
2 | import 'package:benchmark/benchmark.dart';
3 | import 'counter.dart';
4 |
5 | class IncrementerSub extends Incrementer {
6 | IncrementerSub(super.c);
7 |
8 | @override
9 | doJobOverride() {
10 | counter.increment();
11 | }
12 | }
13 |
14 | void main() {
15 | final code = '''
16 | import 'package:test/counter.dart';
17 |
18 | void increment(Counter c) {
19 | c.increment();
20 | }
21 |
22 | class IncrementerSub extends Incrementer {
23 | IncrementerSub(Counter c): super(c);
24 |
25 | @override
26 | doJobSuper() {
27 | super.doJobSuper();
28 | }
29 |
30 | @override
31 | doJobOverride() {
32 | counter.increment();
33 | }
34 | }
35 |
36 | Incrementer construct(Counter c) => IncrementerSub(c);
37 | ''';
38 | final compiler = Compiler();
39 | compiler.addPlugin(CounterPlugin());
40 | final program = compiler.compile({
41 | kPackageName: {'main.dart': code},
42 | });
43 | final runtime = Runtime.ofProgram(program);
44 | runtime.addPlugin(CounterPlugin());
45 |
46 | const kIterations = 100000;
47 | final counter = Counter();
48 | final incrementer = runtime.executeLib(
49 | 'package:$kPackageName/main.dart', 'construct', [$Counter.wrap(counter)]);
50 | final rawIncrementer = IncrementerSub(counter);
51 |
52 | group('Counter tests', () {
53 | setUp(() {
54 | counter.reset();
55 | });
56 |
57 | tearDown(() {
58 | assert(counter.count == kIterations);
59 | });
60 |
61 | benchmark('Raw increment', () {
62 | counter.increment();
63 | }, iterations: kIterations);
64 |
65 | benchmark('Raw class increment', () {
66 | rawIncrementer.doJobOverride();
67 | }, iterations: kIterations);
68 |
69 | benchmark('Eval function', () {
70 | runtime.executeLib('package:test/main.dart', 'increment', [$Counter.wrap(counter)]);
71 | }, iterations: kIterations);
72 |
73 | benchmark('Eval original', () {
74 | incrementer.doJobOriginal();
75 | }, iterations: kIterations);
76 |
77 | benchmark('Eval super', () {
78 | incrementer.doJobSuper();
79 | }, iterations: kIterations);
80 |
81 | benchmark('Eval overidden', () {
82 | incrementer.doJobOverride();
83 | }, iterations: kIterations);
84 | });
85 | }
86 |
--------------------------------------------------------------------------------
/lib/plugins.dart:
--------------------------------------------------------------------------------
1 | import 'package:extension_test/plugin_provider.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class PluginsPage extends StatefulWidget {
5 | const PluginsPage({super.key});
6 |
7 | @override
8 | State createState() => _PluginsPageState();
9 | }
10 |
11 | class _PluginsPageState extends State {
12 | void _repaint() {
13 | if (mounted) {
14 | setState(() {});
15 | }
16 | }
17 |
18 | @override
19 | void initState() {
20 | super.initState();
21 | PluginProvider.instance.addListener(_repaint);
22 | }
23 |
24 | @override
25 | void dispose() {
26 | PluginProvider.instance.removeListener(_repaint);
27 | super.dispose();
28 | }
29 |
30 | List _buildPluginRow(BuildContext context, Plugin plugin) {
31 | final pp = PluginProvider.instance;
32 | final settings = pp.buildSettingsWidget(context, plugin.id);
33 | return [
34 | Dismissible(
35 | key: Key(plugin.id),
36 | direction: DismissDirection.endToStart,
37 | background: Container(
38 | color: Colors.red,
39 | padding: EdgeInsets.only(right: 15.0),
40 | alignment: Alignment.centerRight,
41 | child: Icon(Icons.delete, color: Colors.white),
42 | ),
43 | onDismissed: (_) async {
44 | // Plugin deletion is easily reversible (by installing it anew),
45 | // so we don't ask the user again.
46 | await pp.deletePlugin(plugin.id);
47 | if (mounted) {
48 | setState(() {});
49 | }
50 | },
51 | child: ListTile(
52 | title: Text(plugin.name),
53 | trailing: pp.isActive(plugin.id) ? Icon(Icons.check_circle) : null,
54 | onTap: () async {
55 | // We need [setState], because enabling the plugin may
56 | // change something in the app state or visually.
57 | await pp.toggle(plugin.id);
58 | setState(() {});
59 | },
60 | ),
61 | ),
62 | if (settings != null)
63 | Padding(
64 | padding: const EdgeInsets.only(left: 10.0),
65 | child: settings,
66 | ),
67 | Divider(),
68 | ];
69 | }
70 |
71 | @override
72 | Widget build(BuildContext context) {
73 | final pp = PluginProvider.instance;
74 |
75 | return Scaffold(
76 | appBar: AppBar(title: Text('Plugins')),
77 | body: ListView(
78 | children: [
79 | for (final plugin in pp.all) ..._buildPluginRow(context, plugin),
80 | ],
81 | ),
82 | );
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/plugins/package_plugins.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert' show json;
2 | import 'dart:io';
3 | import 'package:archive/archive_io.dart' show ZipFileEncoder;
4 | import 'package:path/path.dart' as path;
5 | import 'package:dart_eval/dart_eval_bridge.dart';
6 | import 'package:dart_eval/dart_eval.dart' show Compiler;
7 |
8 | Map>? loadPluginData(Directory dir) {
9 | final files = {};
10 |
11 | // Now iterate over files and also replace the models import.
12 | for (final file in dir.listSync().whereType()) {
13 | // We're not doing subdirectories or external packages.
14 | if (file.path.endsWith('.dart')) {
15 | String code = file.readAsStringSync();
16 | code = code.replaceFirst('../models.dart', 'package:plugin/_models.dart');
17 | final fileName = path.relative(file.path, from: dir.path);
18 | files[fileName] = code;
19 | }
20 | }
21 |
22 | // Finally compile.
23 | if (files.isEmpty) return null;
24 | return {'plugin': files};
25 | }
26 |
27 | void loadBindings(Compiler compiler) {
28 | const bindingFiles = ['flutter_eval.json', 'plugin_base.json'];
29 | for (final name in bindingFiles) {
30 | final file = File(name);
31 | if (!file.existsSync()) {
32 | throw FileSystemException('File $name is missing');
33 | }
34 | // Copied from dart_eval compiler.
35 | final data = file.readAsStringSync();
36 | final decoded = (json.decode(data) as Map).cast();
37 | final classList = (decoded['classes'] as List);
38 | for (final $class in classList.cast