diff --git a/CHANGELOG.md b/CHANGELOG.md index 9288825..2cb0b89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 2.0.5 + +### Arcane Framework + +- [NEW] Added `Arcane.service` typed lookup entrypoint for provider-aware + service access: + - `Arcane.service.ofType(context)` + - `Arcane.service.requiredOfType(context)` + ## 2.0.4 ### Logging Service diff --git a/README.md b/README.md index 14ff3d3..461c991 100644 --- a/README.md +++ b/README.md @@ -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(context)` and `requiredOfType(context)`. - The `service` and `requiredService` 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(context); +final FavoriteColorService? nullableViaArcane = Arcane.service.ofType(context); final FavoriteColorService? nullableViaContext = context.service(); final FavoriteColorService? nullableViaProvider = ArcaneServiceProvider.serviceOfType(context); // If a service of the given type is not registered, an exception is thrown. final FavoriteColorService nonNullableService = ArcaneService.requiredOfType(context); +final FavoriteColorService nonNullableViaArcane = Arcane.service.requiredOfType(context); final FavoriteColorService nonNullableViaContext = context.requiredService(); final FavoriteColorService nonNullableViaProvider = ArcaneServiceProvider.requiredServiceOfType(context); ``` diff --git a/example/lib/main.dart b/example/lib/main.dart index 201544d..37338de 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -24,7 +24,9 @@ Future 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; } diff --git a/lib/src/arcane.dart b/lib/src/arcane.dart index 414adec..0c78ec7 100644 --- a/lib/src/arcane.dart +++ b/lib/src/arcane.dart @@ -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(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().firstOrNull ?? diff --git a/lib/src/service/service_provider.dart b/lib/src/service/service_provider.dart index 5f156d7..2f171e8 100644 --- a/lib/src/service/service_provider.dart +++ b/lib/src/service/service_provider.dart @@ -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(BuildContext context) => + ArcaneService.ofType(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(BuildContext context) => + ArcaneService.requiredOfType(context); +} diff --git a/pubspec.yaml b/pubspec.yaml index 23c6afb..83dd2dd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 diff --git a/test/providers/service_provider_test.dart b/test/providers/service_provider_test.dart index a714bf9..869238a 100644 --- a/test/providers/service_provider_test.dart +++ b/test/providers/service_provider_test.dart @@ -325,6 +325,52 @@ void main() { ), ); }); + + testWidgets("Arcane.service.ofType helper works", (tester) async { + await tester.pumpWidget( + ArcaneApp( + services: testServices, + child: Builder( + builder: (context) { + final service = Arcane.service.ofType(context); + expect(service, isNotNull); + expect(service, isA()); + + final missing = Arcane.service.ofType( + context, + ); + expect(missing, isNull); + return const SizedBox(); + }, + ), + ), + ); + }); + + testWidgets("Arcane.service.requiredOfType helper works", + (tester) async { + await tester.pumpWidget( + ArcaneApp( + services: testServices, + child: Builder( + builder: (context) { + final service = Arcane.service.requiredOfType( + context, + ); + expect(service, isA()); + + expect( + () => + Arcane.service.requiredOfType(context), + throwsA(isA()), + ); + + return const SizedBox(); + }, + ), + ), + ); + }); }); }