Skip to content

Commit f456658

Browse files
authored
experiment: External widget lib support (#58)
* chore: Update Flutter SDK and dependencies, add experimental features for external library support - Updated Flutter SDK version from 3.24.3 to 3.27.4. - Adjusted Dart SDK constraints in pubspec.yaml to support Dart 3.6.0. - Updated dependencies in pubspec.lock to their latest versions. - Introduced experimental flags in env.dart for overriding core widget implementations and enabling external library support. - Added new classes and methods in registry.dart and descriptor.dart to manage external widget libraries and their descriptors. - Exported descriptor.dart in index.dart for better accessibility. * upd CI pipelines * Fix bug in tests
1 parent a97f3d7 commit f456658

9 files changed

Lines changed: 271 additions & 21 deletions

File tree

.fvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"flutter": "3.24.3",
2+
"flutter": "3.27.4",
33
"updateVscodeSettings": true,
44
"updateGitIgnore": true,
55
"runPubGetOnSdkChanges": true

.github/workflows/post-merge.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
strategy:
1212
fail-fast: false
1313
matrix:
14-
flutter-version: ["3.24.3", "3.27.4", "3.29.3", "3.32.7"]
14+
flutter-version: ["3.27.4", "3.29.3", "3.32.7"]
1515
steps:
1616
- uses: actions/checkout@v3
1717

.github/workflows/pr.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
strategy:
99
fail-fast: false
1010
matrix:
11-
flutter-version: ["3.24.3", "3.27.4", "3.29.3", "3.32.7"]
11+
flutter-version: ["3.27.4", "3.29.3", "3.32.7"]
1212
steps:
1313
- uses: actions/checkout@v3
1414

example/pubspec.lock

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ packages:
3737
dependency: transitive
3838
description:
3939
name: collection
40-
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
40+
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
4141
url: "https://pub.dev"
4242
source: hosted
43-
version: "1.18.0"
43+
version: "1.19.0"
4444
cupertino_icons:
4545
dependency: "direct main"
4646
description:
@@ -79,18 +79,18 @@ packages:
7979
dependency: transitive
8080
description:
8181
name: leak_tracker
82-
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
82+
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
8383
url: "https://pub.dev"
8484
source: hosted
85-
version: "10.0.5"
85+
version: "10.0.7"
8686
leak_tracker_flutter_testing:
8787
dependency: transitive
8888
description:
8989
name: leak_tracker_flutter_testing
90-
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
90+
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
9191
url: "https://pub.dev"
9292
source: hosted
93-
version: "3.0.5"
93+
version: "3.0.8"
9494
leak_tracker_testing:
9595
dependency: transitive
9696
description:
@@ -143,7 +143,7 @@ packages:
143143
dependency: transitive
144144
description: flutter
145145
source: sdk
146-
version: "0.0.99"
146+
version: "0.0.0"
147147
source_span:
148148
dependency: transitive
149149
description:
@@ -156,10 +156,10 @@ packages:
156156
dependency: transitive
157157
description:
158158
name: stack_trace
159-
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
159+
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
160160
url: "https://pub.dev"
161161
source: hosted
162-
version: "1.11.1"
162+
version: "1.12.0"
163163
stream_channel:
164164
dependency: transitive
165165
description:
@@ -172,10 +172,10 @@ packages:
172172
dependency: transitive
173173
description:
174174
name: string_scanner
175-
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
175+
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
176176
url: "https://pub.dev"
177177
source: hosted
178-
version: "1.2.0"
178+
version: "1.3.0"
179179
term_glyph:
180180
dependency: transitive
181181
description:
@@ -188,10 +188,10 @@ packages:
188188
dependency: transitive
189189
description:
190190
name: test_api
191-
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
191+
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
192192
url: "https://pub.dev"
193193
source: hosted
194-
version: "0.7.2"
194+
version: "0.7.3"
195195
vector_math:
196196
dependency: transitive
197197
description:
@@ -204,10 +204,10 @@ packages:
204204
dependency: transitive
205205
description:
206206
name: vm_service
207-
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
207+
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
208208
url: "https://pub.dev"
209209
source: hosted
210-
version: "14.2.5"
210+
version: "14.3.0"
211211
sdks:
212212
dart: ">=3.4.4 <4.0.0"
213213
flutter: ">=3.18.0-18.0.pre.54"

lib/src/misc/env.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,40 @@ const envPreferInlineFn = bool.fromEnvironment(
77
"duit:prefer-inline",
88
defaultValue: true,
99
);
10+
11+
// ================================================== Experiment flags ==================================================
12+
13+
/// **Experiment** — allows overriding built-in Duit core widget implementations
14+
/// with custom ones at render time.
15+
///
16+
/// When `true`, the element tree builder checks the [DuitRegistry] for a
17+
/// registered [BuildFactory] even for widget types that belong to the Duit core
18+
/// set, letting host apps replace e.g. the built-in `Text` widget with their
19+
/// own version.
20+
///
21+
/// Disabled by default. Enable via
22+
/// `--dart-define=experiment:enable-core-widgets-oveeride=true`.
23+
///
24+
/// > **Warning:** This flag is experimental and may be removed or renamed in a
25+
/// > future release.
26+
const enableCoreWidgetsOveeride = bool.fromEnvironment(
27+
"experiment:enable-core-widgets-oveeride",
28+
defaultValue: false,
29+
);
30+
31+
/// **Experiment** — enables support for external widget libraries loaded via
32+
/// [DuitRegistry.loadLibrary].
33+
///
34+
/// When `true`, the element tree builder consults the library
35+
/// descriptor reverse-index for widget types that are not part of the core set
36+
/// and not registered as plain custom widgets. Set to `true` via
37+
/// `--dart-define=experiment:enable-external-library-support=true` to disable
38+
/// this lookup path entirely (e.g. to save a map lookup in apps that never use
39+
/// external libraries).
40+
///
41+
/// > **Warning:** This flag is experimental and may be removed or renamed in a
42+
/// > future release.
43+
const enableExternalLibrarySupport = bool.fromEnvironment(
44+
"experiment:enable-external-library-support",
45+
defaultValue: false,
46+
);

lib/src/registry_api/registry.dart

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@ import "dart:async";
22

33
import "package:duit_kernel/duit_kernel.dart";
44
import "package:duit_kernel/src/registry_api/components/index.dart";
5+
import "package:meta/meta.dart";
6+
7+
const _messageExternalLibrarySupportNotEnabled =
8+
"External library support is not enabled";
59

610
/// The [DuitRegistry] class is responsible for registering and retrieving
711
/// model factories, build factories, and attributes factories for custom DUIT elements.
812
sealed class DuitRegistry {
913
static final Map<String, BuildFactory> _customComponentRegistry = {};
1014
static final Map<String, Map<String, dynamic>> _fragmentRegistry = {};
11-
static LoggingCapabilityDelegate _logManager = const LoggingManager();
1215
static ComponentRegistry _componentRegistry = DefaultComponentRegistry();
1316
static DuitTheme _theme = const DuitTheme({});
17+
static LoggingCapabilityDelegate _logManager = const LoggingManager();
18+
static late final Map<String, LibraryDescriptor> _libraries;
19+
static late final Map<String, LibraryDescriptor> _reverseIndex;
1420

1521
static FutureOr<void> initialize({
1622
@Deprecated("Will be removed in the next major release.")
@@ -23,6 +29,11 @@ sealed class DuitRegistry {
2329
_theme = theme ?? _theme;
2430
_logManager = logManager ?? _logManager;
2531
await _componentRegistry.init();
32+
33+
if (enableExternalLibrarySupport) {
34+
_libraries = {};
35+
_reverseIndex = {};
36+
}
2637
}
2738

2839
static DuitTheme get theme => _theme;
@@ -155,4 +166,68 @@ sealed class DuitRegistry {
155166
DuitDataSource.registerCustomObjectFactory(toClass);
156167
_logManager.logInfo("Custom object factory for $T registered successfully");
157168
}
169+
170+
/// Loads an external [LibraryDescriptor] into the registry.
171+
///
172+
/// The [library] argument must be a compile-time constant (`@mustBeConst`).
173+
/// After loading, every [WidgetDescriptor] declared in
174+
/// [LibraryDescriptor.descriptors] is indexed in the internal reverse-lookup
175+
/// map so that [getDescriptor] and [hasDescriptor] can resolve widget types
176+
/// at O(1) cost.
177+
///
178+
/// Loading the same library name twice is a no-op (an error is logged and
179+
/// the call returns immediately). Similarly, if a widget type key from the
180+
/// new library already exists in the reverse index (registered by a
181+
/// previously loaded library), an error is logged and that descriptor is
182+
/// skipped.
183+
///
184+
/// Example:
185+
/// ```dart
186+
/// DuitRegistry.loadLibrary(const MyWidgetLibrary());
187+
/// ```
188+
@experimental
189+
static void loadLibrary(@mustBeConst LibraryDescriptor library) {
190+
if (enableExternalLibrarySupport) {
191+
if (_libraries.containsKey(library.name)) {
192+
_logManager.logError("Library ${library.name} already loaded");
193+
return;
194+
}
195+
_libraries[library.name] = library;
196+
for (final key in library.descriptors.keys) {
197+
_reverseIndex[key] = library;
198+
}
199+
} else {
200+
_logManager.logCritical(_messageExternalLibrarySupportNotEnabled);
201+
throw UnsupportedError(_messageExternalLibrarySupportNotEnabled);
202+
}
203+
}
204+
205+
/// Returns the [WidgetDescriptor] for [name] from the loaded external
206+
/// libraries, or `null` if no descriptor with that widget type has been
207+
/// registered.
208+
///
209+
/// Uses the internal reverse-index built by [loadLibrary] for O(1) lookup.
210+
@experimental
211+
@preferInline
212+
static WidgetDescriptor? getDescriptor(String name) {
213+
if (enableExternalLibrarySupport) {
214+
return _reverseIndex[name]?.getDescriptor(name);
215+
} else {
216+
_logManager.logCritical(_messageExternalLibrarySupportNotEnabled);
217+
throw UnsupportedError(_messageExternalLibrarySupportNotEnabled);
218+
}
219+
}
220+
221+
/// Returns `true` if an external library loaded via [loadLibrary] contains a
222+
/// [WidgetDescriptor] whose type equals [name].
223+
@experimental
224+
@preferInline
225+
static bool hasDescriptor(String name) {
226+
if (enableExternalLibrarySupport) {
227+
return _reverseIndex[name]?.hasDescriptor(name) ?? false;
228+
} else {
229+
_logManager.logCritical(_messageExternalLibrarySupportNotEnabled);
230+
throw UnsupportedError(_messageExternalLibrarySupportNotEnabled);
231+
}
232+
}
158233
}

0 commit comments

Comments
 (0)