v2.0.5: Add typed service lookup entrypoint and tests for Arcane.service

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-06-03 19:37:14 +02:00
parent fe42eac0dc
commit cbb1a78473
7 changed files with 83 additions and 2 deletions
+9
View File
@@ -1,3 +1,12 @@
## 2.0.5
### Arcane Framework
- [NEW] Added `Arcane.service` typed lookup entrypoint for provider-aware
service access:
- `Arcane.service.ofType<T>(context)`
- `Arcane.service.requiredOfType<T>(context)`
## 2.0.4
### Logging Service
+4
View File
@@ -169,6 +169,8 @@ services:
`ArcaneService` instances. **Note**: This widget is already part of the
_`ArcaneApp`_ widget, however if you are not using the `ArcaneApp` widget you
can instead use this widget directly.
- `Arcane.service`: Typed service lookup entrypoint with
`ofType<T>(context)` and `requiredOfType<T>(context)`.
- The `service<T>` and `requiredService<T>` extensions on `BuildContext`:
nullable and non-nullable getters used to locate a given `ArcaneService` via
`BuildContext`. **Note**: For app-defined services, these lookups require an
@@ -255,11 +257,13 @@ whatever method you prefer:
```dart
// If a service of the given type is not registered, `null` is returned.
final FavoriteColorService? nullableService = ArcaneService.ofType<FavoriteColorService>(context);
final FavoriteColorService? nullableViaArcane = Arcane.service.ofType<FavoriteColorService>(context);
final FavoriteColorService? nullableViaContext = context.service<FavoriteColorService>();
final FavoriteColorService? nullableViaProvider = ArcaneServiceProvider.serviceOfType<FavoriteColorService>(context);
// If a service of the given type is not registered, an exception is thrown.
final FavoriteColorService nonNullableService = ArcaneService.requiredOfType<FavoriteColorService>(context);
final FavoriteColorService nonNullableViaArcane = Arcane.service.requiredOfType<FavoriteColorService>(context);
final FavoriteColorService nonNullableViaContext = context.requiredService<FavoriteColorService>();
final FavoriteColorService nonNullableViaProvider = ArcaneServiceProvider.requiredServiceOfType<FavoriteColorService>(context);
```
+3 -1
View File
@@ -24,7 +24,9 @@ Future<void> main() async {
final DebugPrint debugPrintInterface = DebugPrint();
final DebugAuthInterface debugAuthInterface = DebugAuthInterface();
LogEvent? skipDebugPrintWhenDisabled(
LogEvent event, LogInterceptorContext context) {
LogEvent event,
LogInterceptorContext context,
) {
if (context.interface is DebugPrint && Feature.logging.disabled) {
return null;
}
+3
View File
@@ -43,6 +43,9 @@ abstract class Arcane {
/// Logger is not a service and is always the singleton.
static ArcaneLogger get logger => ArcaneLogger.I;
/// Provides typed service lookups through `Arcane.service.ofType<T>(context)`.
static const ArcaneServiceLookup service = ArcaneServiceLookup();
/// Provides access to the feature flags service instance registered in ArcaneApp, or the singleton if not present.
static ArcaneFeatureFlagService get features =>
services.whereType<ArcaneFeatureFlagService>().firstOrNull ??
+17
View File
@@ -144,3 +144,20 @@ class ArcaneServiceProvider
return true;
}
}
/// Typed service lookup entrypoint for `Arcane.service`.
class ArcaneServiceLookup {
const ArcaneServiceLookup();
/// Returns a service of type `T` from the closest provider in [context].
///
/// Falls back to Arcane's built-in services when no provider instance exists.
T? ofType<T extends ArcaneService>(BuildContext context) =>
ArcaneService.ofType<T>(context);
/// Returns a service of type `T` from the closest provider in [context].
///
/// Throws an assertion error if no matching service is available.
T requiredOfType<T extends ArcaneService>(BuildContext context) =>
ArcaneService.requiredOfType<T>(context);
}
+1 -1
View File
@@ -1,7 +1,7 @@
name: arcane_framework
description: "Agnostic Reusable Component Architecture for New Ecosystems: a
modern framework for bootstrapping new applications"
version: 2.0.4
version: 2.0.5
repository: https://github.com/hanskokx/arcane_framework
issue_tracker: https://github.com/hanskokx/arcane_framework/issues
+46
View File
@@ -325,6 +325,52 @@ void main() {
),
);
});
testWidgets("Arcane.service.ofType<T> helper works", (tester) async {
await tester.pumpWidget(
ArcaneApp(
services: testServices,
child: Builder(
builder: (context) {
final service = Arcane.service.ofType<MockArcaneService>(context);
expect(service, isNotNull);
expect(service, isA<MockArcaneService>());
final missing = Arcane.service.ofType<UnregisteredService>(
context,
);
expect(missing, isNull);
return const SizedBox();
},
),
),
);
});
testWidgets("Arcane.service.requiredOfType<T> helper works",
(tester) async {
await tester.pumpWidget(
ArcaneApp(
services: testServices,
child: Builder(
builder: (context) {
final service = Arcane.service.requiredOfType<MockArcaneService>(
context,
);
expect(service, isA<MockArcaneService>());
expect(
() =>
Arcane.service.requiredOfType<UnregisteredService>(context),
throwsA(isA<AssertionError>()),
);
return const SizedBox();
},
),
),
);
});
});
}