From cd6bad6fecc05c1b7d0066946d17bc68b5049a1a Mon Sep 17 00:00:00 2001 From: Alexander Bangert Date: Sun, 8 Dec 2024 02:51:47 +0500 Subject: [PATCH 1/7] factory --- MIGRATION.md | 151 +++++++++++++++++++++++++ README.md | 169 ++++++---------------------- coverage/lcov.info | 102 +++++++---------- example/lib/main.dart | 12 +- example/lib/src/dependencies.dart | 48 +++++--- example/pubspec.lock | 2 +- lib/depend.dart | 2 +- lib/src/context_extension.dart | 5 +- lib/src/dependency_container.dart | 75 +----------- lib/src/dependency_factory.dart | 5 + lib/src/dependency_provider.dart | 11 +- lib/src/dependency_scope.dart | 42 ++++--- lib/src/injection_exception.dart | 46 -------- test/dependency_container_test.dart | 56 +-------- test/dependency_provider_test.dart | 82 +++++++------- test/dependency_scope_test.dart | 76 +++++-------- 16 files changed, 366 insertions(+), 518 deletions(-) create mode 100644 MIGRATION.md create mode 100644 lib/src/dependency_factory.dart delete mode 100644 lib/src/injection_exception.dart diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..f96d6d1 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,151 @@ +## [v4 to v5](#from-version-4-to-version-5) +## [v4 to v5](#from-version-3-to-version-4) + + +## From Version 4 to Version 5 + +### Version 4: + +```dart +class RootFactory extends DependencyFactory { + @override + Future create() async { + return RootDependency( + apiService: await ApiService().init(), + ); + } +} +``` + +```dart +class RootDependency extends DependencyContainer { + final AuthRepository authRepository; + + ModuleDependency({required super.authRepository}); + + void dispose() { + // authRepository.dispose(); + } +} +``` + +```dart +DependencyScope( + factory: RootFactory(), + placeholder: const Center(child: CircularProgressIndicator()), + builder: (context) => const YourWidget(), +); +``` + + +```dart +final rootDependency = await RootFactory().create(); + +DependencyProvider( + dependency: rootDependency, + child: const YourWidget(), +); +``` + + + +### Version 5: + +```dart +DependencyScope( + dependency: RootDependency(), + placeholder: const Center(child: CircularProgressIndicator()), + builder: (context) => const YourWidget(), +); +``` + +```dart +class ModuleDependency extends DependencyContainer { + late final AuthRepository authRepository; + + ModuleDependency({required super.parent}); + + @override + Future init() async { + authRepository = AuthRepository( + apiService: parent.apiService, + ); + } + + void dispose() { + // authRepository.dispose(); + } +} +``` + +```dart +DependencyProvider.of(context); +DependencyProvider.maybeOf(context); +// or +context.depend(); +context.dependMaybe(); +``` + +## From Version 3 to Version 4 + +### Version 3: + +```dart +InjectionScope( + library: RootLibrary(), + placeholder: const Center(child: CircularProgressIndicator()), + child: const YourWidget(), +); +``` + +```dart +class RootInjection extends Injection { + late final ApiService apiService; + + @override + Future init() async { + apiService = await ApiService().init(); + } +} +``` + +```dart +InjectionScope.of(context); +``` + +### Version 4: + +```dart +DependencyScope( + dependency: RootDependency(), + placeholder: const Center(child: CircularProgressIndicator()), + builder: (context) => const YourWidget(), +); +``` + +```dart +class ModuleDependency extends DependencyContainer { + late final AuthRepository authRepository; + + ModuleDependency({required super.parent}); + + @override + Future init() async { + authRepository = AuthRepository( + apiService: parent.apiService, + ); + } + + void dispose() { + // authRepository.dispose(); + } +} +``` + +```dart +DependencyProvider.of(context); +DependencyProvider.maybeOf(context); +// or +context.depend(); +context.dependMaybe(); +``` diff --git a/README.md b/README.md index f9a15c7..b25f03c 100644 --- a/README.md +++ b/README.md @@ -24,15 +24,6 @@ Here’s the translated and updated `README.md` for the `depend` library: ## Table of Contents -- [Installation](#installation) -- [Usage Examples](#usage-examples) - - [Example 1: Simple Initialization](#example-1-simple-initialization) - - [Example 2: Parent Dependencies](#example-2-parent-dependencies) - - [Example 3: DependencyScope](#example-3-dependencyscope) -- [Migration Guide](#migration-guide) - - [From Version 3 to Version 4](#from-version-3-to-version-4) -- [Code Coverage](#code-coverage) - --- ## Installation @@ -62,12 +53,10 @@ Create a class that extends `DependencyContainer` and initialize your dependenci ```dart class RootDependency extends DependencyContainer { - late final ApiService apiService; + final ApiService apiService; + + RootDependency({required this.apiService}); - @override - Future init() async { - apiService = await ApiService().init(); - } void dispose() { // apiService.dispose() @@ -75,15 +64,31 @@ class RootDependency extends DependencyContainer { } ``` -#### Step 2: Use `DependencyScope` + +#### Step 2: Define the DependencyFactory + +Create a class that extends `DependencyContainer` and initialize your dependencies: + +```dart +class RootDependencyFactory extends DependencyFactory { + + Future create() async { + return RootDependency( + apiService: await ApiService.initialize(), + ); + } +} +``` + +#### Step 3: Use `DependencyScope` Wrap your app in a `DependencyScope` to provide dependencies: ```dart void main() { runApp( - DependencyScope( - dependency: RootDependency(), + DependencyScope( + dependency: RootDependencyFactory(), placeholder: const Center(child: CircularProgressIndicator()), builder: (BuildContext context) => const MyApp(), ), @@ -91,7 +96,7 @@ void main() { } ``` -#### Step 3: Access the Dependency in a Widget +#### Step 4: Access the Dependency in a Widget You can now access the dependency using `DependencyProvider` anywhere in the widget tree: @@ -119,138 +124,36 @@ class MyWidget extends StatelessWidget { --- -### Example 2: Parent Dependencies - -#### Step 1: Create the Parent Dependency +### Example 2: ```dart -class RootDependency extends DependencyContainer { - late final ApiService apiService; - - @override - Future init() async { - apiService = await ApiService().init(); - } -} -``` - -#### Step 2: Create the Child Dependency - -Use the parent dependency inside the child: - -```dart -class ModuleDependency extends DependencyContainer { - late final AuthRepository authRepository; - - ModuleDependency({required super.parent}); - - @override - Future init() async { - authRepository = AuthRepository( - apiService: parent.apiService, - ); - } -} -``` - -#### Step 3: Link Both Dependencies - -```dart -void main() { - runApp( - DependencyScope( - dependency: RootDependency(), - builder: (BuildContext context) => DependencyScope( - dependency: ModuleDependency( - parent: DependencyProvider.of(context), - // or - // parent: context.depend(), - ), - builder: (BuildContext context) => const MyApp(), - ), - ), - ); -} +final RootDependency dep = await RootFactory().create(); + +DependencyProvider( + dependency: dep, + builder: (context) => YourWidget(); + // or + child: YourWidget() +) ``` ---- - ### Example 3: DependencyScope ```dart -DependencyScope( - dependency: RootDependency(), +DependencyScope( + factory: RootFactory(), builder: (BuildContext context) => Text('Inject'), placeholder: Text('Placeholder'), errorBuilder: (Object? error) => Text('Error'), ), ``` -## Migration Guide - -### From Version 3 to Version 4 - -#### Version 3: -```dart -InjectionScope( - library: RootLibrary(), - placeholder: const Center(child: CircularProgressIndicator()), - child: const YourWidget(), -); -``` -```dart -class RootInjection extends Injection { - late final ApiService apiService; - - @override - Future init() async { - apiService = await ApiService().init(); - } -} -``` - -```dart -InjectionScope.of(context); -``` - -#### Version 4: - -```dart -DependencyScope( - dependency: RootDependency(), - placeholder: const Center(child: CircularProgressIndicator()), - builder: (context) => const YourWidget(), -); -``` - -```dart -class ModuleDependency extends DependencyContainer { - late final AuthRepository authRepository; - - ModuleDependency({required super.parent}); - - @override - Future init() async { - authRepository = AuthRepository( - apiService: parent.apiService, - ); - } +## Migration Guide - void dispose() { - // authRepository.dispose(); - } -} -``` +[link to migrate versions](MIGRATION.md) -```dart -DependencyProvider.of(context); -DependencyProvider.maybeOf(context); -// or -context.depend(); -context.dependMaybe(); -``` #### Key Differences: - `InjectionScope` → `DependencyScope` diff --git a/coverage/lcov.info b/coverage/lcov.info index 6c99493..2cdcebb 100644 --- a/coverage/lcov.info +++ b/coverage/lcov.info @@ -1,27 +1,14 @@ SF:lib/src/dependency_container.dart -DA:39,1 -DA:51,1 -DA:53,1 -DA:54,1 -DA:55,2 -DA:56,1 -DA:59,1 -DA:69,2 -DA:99,1 -DA:100,1 -DA:101,1 -DA:102,1 -DA:121,1 -LF:13 -LH:13 +DA:48,2 +LF:1 +LH:1 end_of_record SF:lib/src/context_extension.dart -DA:17,1 -DA:18,1 +DA:17,2 +DA:33,1 DA:34,1 -DA:35,1 -LF:4 -LH:4 +LF:3 +LH:3 end_of_record SF:lib/src/dependency_provider.dart DA:34,2 @@ -33,53 +20,44 @@ DA:72,1 DA:73,1 DA:92,1 DA:96,2 -DA:99,1 +DA:99,2 DA:100,1 -DA:101,1 -DA:109,1 -DA:111,3 -LF:14 -LH:14 +DA:108,1 +DA:110,3 +LF:13 +LH:13 end_of_record SF:lib/src/dependency_scope.dart -DA:39,1 -DA:61,1 -DA:62,1 -DA:69,1 +DA:37,1 +DA:58,1 +DA:59,1 +DA:66,1 +DA:68,1 DA:71,1 -DA:72,2 -DA:75,1 -DA:77,1 -DA:80,4 -DA:81,2 -DA:82,2 -DA:86,1 -DA:89,3 -DA:90,1 +DA:73,1 +DA:76,4 +DA:77,3 +DA:81,1 +DA:84,3 +DA:85,1 +DA:88,1 +DA:89,1 +DA:90,3 +DA:91,1 +DA:92,1 DA:94,4 -DA:96,1 -DA:97,1 +DA:95,2 DA:98,1 -DA:99,1 -DA:100,1 -DA:102,4 -DA:103,2 +DA:99,2 +DA:102,1 +DA:104,1 +DA:105,1 DA:106,1 -DA:108,1 -DA:109,1 -DA:110,1 -DA:111,2 -DA:114,2 -DA:115,2 -DA:116,1 -DA:117,2 -LF:31 -LH:31 -end_of_record -SF:lib/src/injection_exception.dart -DA:26,1 -DA:43,1 -DA:45,5 -LF:3 -LH:3 +DA:107,2 +DA:110,2 +DA:111,1 +DA:112,1 +DA:113,2 +LF:30 +LH:30 end_of_record diff --git a/example/lib/main.dart b/example/lib/main.dart index 479ea40..f8f527e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -17,15 +17,15 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', - home: DependencyScope( - dependency: RootInjection(), + home: DependencyScope( + factory: RootFactory(), placeholder: const ColoredBox( color: Colors.white, child: CupertinoActivityIndicator(), ), - builder: (context) => DependencyScope( - dependency: ModuleInjection( - parent: DependencyProvider.of(context), + builder: (context) => DependencyScope( + factory: ModuleFactory( + rootInjection: DependencyProvider.of(context), ), placeholder: const ColoredBox( color: Colors.white, @@ -33,7 +33,7 @@ class MyApp extends StatelessWidget { ), builder: (context) => BlocProvider( create: (context) => DefaultBloc( - DependencyProvider.of(context).authRepository, + DependencyProvider.of(context).authRepository, ), child: const MyHomePage(), ), diff --git a/example/lib/src/dependencies.dart b/example/lib/src/dependencies.dart index 76d5d10..c24a5b4 100644 --- a/example/lib/src/dependencies.dart +++ b/example/lib/src/dependencies.dart @@ -1,32 +1,46 @@ - import 'package:depend/depend.dart'; import 'package:example/src/services.dart'; -class RootInjection extends DependencyContainer { - late final ApiService apiService; +class RootDependency extends DependencyContainer { + final ApiService apiService; - @override - Future init() async { - apiService = await ApiService().init(); - } + RootDependency({required this.apiService}); } -class ModuleInjection extends DependencyContainer { - late final AuthRepository authRepository; +class ModuleDependency extends DependencyContainer { + final AuthRepository authRepository; - ModuleInjection({required super.parent}); + ModuleDependency({required this.authRepository}); @override - Future init() async { - authRepository = AuthRepository( - dataSource: AuthDataSource( - apiService: parent.apiService, - ), + void dispose() { + authRepository.dispose(); + } +} + +class RootFactory extends DependencyFactory { + @override + Future create() async { + return RootDependency( + apiService: await ApiService().init(), ); } +} + +class ModuleFactory extends DependencyFactory { + final RootDependency _rootInjection; + + ModuleFactory({required RootDependency rootInjection}) + : _rootInjection = rootInjection; @override - void dispose() { - authRepository.dispose(); + Future create() async { + return ModuleDependency( + authRepository: AuthRepository( + dataSource: AuthDataSource( + apiService: _rootInjection.apiService, + ), + ), + ); } } diff --git a/example/pubspec.lock b/example/pubspec.lock index 2c5dc1a..1a0e718 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -63,7 +63,7 @@ packages: path: ".." relative: true source: path - version: "4.0.0-dev" + version: "4.0.0" fake_async: dependency: transitive description: diff --git a/lib/depend.dart b/lib/depend.dart index a0b6278..1267733 100644 --- a/lib/depend.dart +++ b/lib/depend.dart @@ -2,6 +2,6 @@ library; export 'src/context_extension.dart'; export 'src/dependency_container.dart'; +export 'src/dependency_factory.dart'; export 'src/dependency_provider.dart'; export 'src/dependency_scope.dart'; -export 'src/injection_exception.dart'; diff --git a/lib/src/context_extension.dart b/lib/src/context_extension.dart index 284cc2b..437457d 100644 --- a/lib/src/context_extension.dart +++ b/lib/src/context_extension.dart @@ -14,8 +14,7 @@ extension DependencyContext on BuildContext { /// ```dart /// MyDependencyContainer myDependency = context.depend(); /// ``` - T depend>() => - DependencyProvider.of(this); + T depend() => DependencyProvider.of(this); /// Retrieves a dependency of type [T] from the nearest [DependencyProvider] /// in the widget tree, or returns `null` if no matching [DependencyProvider] @@ -31,6 +30,6 @@ extension DependencyContext on BuildContext { /// // Use the dependency /// } /// ``` - T? maybeDepend>() => + T? maybeDepend() => DependencyProvider.maybeOf(this); } diff --git a/lib/src/dependency_container.dart b/lib/src/dependency_container.dart index 52d8569..afc4e38 100644 --- a/lib/src/dependency_container.dart +++ b/lib/src/dependency_container.dart @@ -1,6 +1,3 @@ -import 'package:depend/depend.dart'; -import 'package:flutter/foundation.dart'; - /// {@template dependencies_Injection} /// An abstract class that serves as a foundation for managing dependencies /// in a hierarchical and structured way. @@ -31,77 +28,7 @@ import 'package:flutter/foundation.dart'; /// } /// ``` /// {@endtemplate} -abstract class DependencyContainer { - /// Creates an instance of [DependencyContainer]. - /// - /// - The [parent] parameter allows this container to reference another - /// container as its parent, enabling hierarchical dependency setups. - DependencyContainer({T? parent}) : _parent = parent; - - final T? _parent; - - /// Provides access to the parent dependency container. - /// - /// Throws an [InjectionException] if the parent is not set or initialized. - /// - /// ### Example - /// ```dart - /// final parentContainer = parent; - /// ``` - @nonVirtual - T get parent { - if (_parent == null) { - throw InjectionException( - 'Parent in $runtimeType is not initialized', - stackTrace: StackTrace.current, - ); - } - return _parent!; - } - - /// Tracks whether the container's initialization logic has been executed. - /// - /// This ensures that the [init] method is called only once during the - /// container's lifecycle. - bool _isInitialization = false; - - /// Returns `true` if the container has been initialized. - bool get isInitialization => _isInitialization; - - /// The entry point for initializing dependencies within this container. - /// - /// - This method should be overridden by subclasses to provide custom - /// initialization logic. - /// - Ensure you call `super.init()` when overriding to maintain the - /// container's initialization state. - /// - /// ### Example - /// ```dart - /// @override - /// Future init() async { - /// await super.init(); - /// // Custom initialization logic - /// someDependency = await initializeDependency(); - /// } - /// ``` - @mustCallSuper - Future init(); - - /// A wrapper method that ensures [init] is called only once. - /// - /// This method checks if the container is already initialized and skips - /// the initialization if it is. - /// - /// ### Example - /// ```dart - /// await dependencyContainer.inject(); - /// ``` - Future inject() async { - if (_isInitialization) return; - _isInitialization = true; - await init(); - } - +abstract class DependencyContainer { /// Cleans up resources or dependencies managed by this container. /// /// Override this method to perform any necessary cleanup, such as closing diff --git a/lib/src/dependency_factory.dart b/lib/src/dependency_factory.dart new file mode 100644 index 0000000..4714acc --- /dev/null +++ b/lib/src/dependency_factory.dart @@ -0,0 +1,5 @@ +import 'package:depend/depend.dart'; + +abstract class DependencyFactory { + Future create(); +} diff --git a/lib/src/dependency_provider.dart b/lib/src/dependency_provider.dart index 20af52e..16a67bd 100644 --- a/lib/src/dependency_provider.dart +++ b/lib/src/dependency_provider.dart @@ -21,7 +21,7 @@ import 'package:flutter/widgets.dart'; /// ```dart /// final myDependency = DependencyProvider.of(context); /// ``` -class DependencyProvider> +class DependencyProvider extends InheritedWidget { /// Creates a [DependencyProvider] widget. /// @@ -60,7 +60,7 @@ class DependencyProvider> /// // Use the dependency /// } /// ``` - static T? maybeOf>( + static T? maybeOf( BuildContext context, { bool listen = false, }) => @@ -89,16 +89,15 @@ class DependencyProvider> /// final myDependency = DependencyProvider.of(context); /// // Use the dependency /// ``` - static T of>( + static T of( BuildContext context, { bool listen = false, }) => maybeOf(context, listen: listen) ?? _notFound(); /// Helper method to throw an error when a dependency of type [T] is not found. - static Never _notFound>() => - throw ArgumentError( - 'DependencyProvider.of<$T>() called with a context that does not contain an $T.'); + static Never _notFound() => throw ArgumentError( + 'DependencyProvider.of<$T>() called with a context that does not contain an $T.'); /// Determines whether widgets that depend on this [DependencyProvider] should rebuild. /// diff --git a/lib/src/dependency_scope.dart b/lib/src/dependency_scope.dart index c6ea276..cc2b4db 100644 --- a/lib/src/dependency_scope.dart +++ b/lib/src/dependency_scope.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:depend/depend.dart'; import 'package:flutter/widgets.dart'; @@ -24,8 +22,8 @@ import 'package:flutter/widgets.dart'; /// }, /// ); /// ``` -class DependencyScope> - extends StatefulWidget { +class DependencyScope> extends StatefulWidget { /// Creates a [DependencyScope] widget. /// /// - The [dependency] parameter specifies the [DependencyContainer] instance @@ -37,15 +35,14 @@ class DependencyScope> /// - The optional [errorBuilder] is called if an error occurs during /// initialization. const DependencyScope({ - required this.dependency, + required this.factory, required this.builder, this.placeholder, this.errorBuilder, super.key, }); - /// The dependency to be managed and provided. - final T dependency; + final F factory; /// A builder function that constructs the widget tree once the dependency /// has been initialized. @@ -59,43 +56,38 @@ class DependencyScope> final Widget Function(Object? error)? errorBuilder; @override - State> createState() => _DependencyScopeState(); + State> createState() => _DependencyScopeState(); } -class _DependencyScopeState> - extends State> { - late Future _initFuture; +class _DependencyScopeState> extends State> { + T? _dependency; @override void initState() { super.initState(); - _initFuture = _initializeInit(); } @override - void didUpdateWidget(covariant DependencyScope oldWidget) { + void didUpdateWidget(covariant DependencyScope oldWidget) { super.didUpdateWidget(oldWidget); // Dispose of the old dependency and initialize the new one if it has changed. - if (widget.dependency != oldWidget.dependency) { - oldWidget.dependency.dispose(); - _initFuture = _initializeInit(); + if (widget.factory != oldWidget.factory) { + _dependency?.dispose.call(); } } @override void dispose() { // Dispose of the managed dependency when the widget is removed from the tree. - widget.dependency.dispose(); + _dependency?.dispose.call(); super.dispose(); } - /// Initializes the dependency using its [inject] method. - Future _initializeInit() => widget.dependency.inject(); - @override - Widget build(BuildContext context) => FutureBuilder( - future: _initFuture, + Widget build(BuildContext context) => FutureBuilder( + future: widget.factory.create(), builder: (context, snapshot) { if (snapshot.hasError) { // Display the error widget if an error occurs during initialization. @@ -103,6 +95,10 @@ class _DependencyScopeState> ErrorWidget(snapshot.error!); } + if (snapshot.hasData) { + _dependency = snapshot.requireData; + } + return switch (snapshot.connectionState) { // Show the placeholder while waiting for the dependency to initialize. ConnectionState.none || @@ -112,7 +108,7 @@ class _DependencyScopeState> // Provide the dependency once initialization is complete. ConnectionState.done => DependencyProvider( - dependency: widget.dependency, + dependency: snapshot.requireData, child: Builder( builder: widget.builder, ), diff --git a/lib/src/injection_exception.dart b/lib/src/injection_exception.dart deleted file mode 100644 index 9f2159c..0000000 --- a/lib/src/injection_exception.dart +++ /dev/null @@ -1,46 +0,0 @@ -/// {@template injection_exception} -/// An exception used for handling errors related to dependency injection -/// in the application. -/// -/// This class implements the [Exception] interface and allows an additional -/// message to be passed that describes the cause of the error when creating -/// an instance of the exception. -/// -/// It also stores the [stackTrace], which provides additional context -/// about where the exception occurred. -/// -/// Example: -/// ```dart -/// try { -/// throw InjectionException('Error initializing dependency'); -/// } catch (e, stack) { -/// throw InjectionException('Error initializing dependency', stackTrace: stack); -/// } -/// ``` -/// {@endtemplate} -class InjectionException implements Exception { - /// {@macro injection_exception} - /// - /// Constructor that takes a [message] — a string message about the error, - /// and an optional [stackTrace], which provides details of where the error occurred. - InjectionException(this.message, {this.stackTrace}); - - /// {@template message} - /// The error [message] related to dependency injection. - /// - /// This message is available when handling the exception and can be used - /// for logging or displaying to the user. - /// {@endtemplate} - final String message; - - /// {@template stackTrace} - /// The [stackTrace] that provides context about where the exception occurred. - /// - /// This is particularly useful for debugging and logging purposes. - /// {@endtemplate} - final StackTrace? stackTrace; - - @override - String toString() => - 'InjectionException: $message${stackTrace != null ? '\n$stackTrace' : ''}'; -} diff --git a/test/dependency_container_test.dart b/test/dependency_container_test.dart index be99d86..c368052 100644 --- a/test/dependency_container_test.dart +++ b/test/dependency_container_test.dart @@ -2,9 +2,7 @@ import 'package:depend/depend.dart'; import 'package:flutter_test/flutter_test.dart'; /// Тестовая реализация DependencyContainer -class TestDependencyContainer - extends DependencyContainer { - TestDependencyContainer({super.parent}); +class TestDependencyContainer extends DependencyContainer { bool disposed = false; @override @@ -13,34 +11,10 @@ class TestDependencyContainer super.dispose(); } - @override - Future init() async {} } void main() { group('DependencyContainer', () { - test('should provide access to parent if initialized', () { - final parentContainer = TestDependencyContainer(); - final childContainer = TestDependencyContainer(parent: parentContainer); - - expect(childContainer.parent, equals(parentContainer)); - }); - - test('should throw InjectionException if parent is not initialized', () { - final container = TestDependencyContainer(); - - expect(() => container.parent, throwsA(isA())); - }); - - test('should call init and set initialized to true', () async { - final container = TestDependencyContainer(); - - expect(container.isInitialization, isFalse); - - await container.inject(); - - expect(container.isInitialization, isTrue); - }); test('should call dispose and set disposed to true', () { final container = TestDependencyContainer(); @@ -52,33 +26,5 @@ void main() { expect(container.disposed, isTrue); }); - test('take parent then parent null', () { - final container = TestDependencyContainer(); - - expect(() => container.parent, throwsA(isA())); - try { - container.parent; - } catch (err) { - expect(err.toString(), isA()); - } - - container.dispose(); - }); - - test('isInitialization', () async { - final container = TestDependencyContainer(); - - expect(container.isInitialization, isFalse); - - await container.inject(); - - expect(container.isInitialization, isTrue); - - await container.inject(); - - expect(container.isInitialization, isTrue); - - container.dispose(); - }); }); } diff --git a/test/dependency_provider_test.dart b/test/dependency_provider_test.dart index 2687669..537a11d 100644 --- a/test/dependency_provider_test.dart +++ b/test/dependency_provider_test.dart @@ -1,34 +1,39 @@ import 'package:depend/depend.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; // Мок зависимости -class MockDependencyContainer extends Mock - implements DependencyContainer { - late String value; +class MockDependencyContainer extends DependencyContainer { + MockDependencyContainer(this.value); + + String value; +} + +class MockDependencyFactory extends DependencyFactory { + MockDependencyFactory({this.value = 'Value'}); + + final String value; + + @override + Future create() async => + MockDependencyContainer(value); } void main() { group('DependencyScope Tests', () { testWidgets('retrieves dependency correctly', (tester) async { // Создание контейнера с зависимостью - final mockDependency = MockDependencyContainer(); + final factory = MockDependencyFactory(value: 'Test Dependency'); + final dependency = await factory.create(); final k1 = GlobalKey(); final mkey = GlobalKey(); - when(mockDependency.init).thenAnswer((_) async { - mockDependency.value = 'Test Dependency'; - }); - - await mockDependency.init(); - // Строим виджет с DependencyScope await tester.pumpWidget( MaterialApp( key: mkey, home: DependencyProvider( - dependency: mockDependency, + dependency: dependency, child: Builder( builder: (context) { final dependency = @@ -43,8 +48,7 @@ void main() { // Проверяем, что текст "Test Dependency" отображается expect(find.text('Test Dependency'), findsOneWidget); - expect( - k1.currentContext!.depend(), mockDependency); + expect(k1.currentContext!.depend(), dependency); expect( mkey.currentContext?.maybeDepend(), isNull); }); @@ -94,19 +98,12 @@ void main() { testWidgets('updateShouldNotify returns true if dependency changes', (tester) async { - // Строим виджет с DependencyScope - final mockDependency1 = MockDependencyContainer(); - final mockDependency2 = MockDependencyContainer(); - - when(mockDependency1.init).thenAnswer((_) async { - mockDependency1.value = 'Dependency 1'; - }); - when(mockDependency2.init).thenAnswer((_) async { - mockDependency2.value = 'Dependency 2'; - }); + final factory1 = MockDependencyFactory(value: 'Dependency 1'); + final factory2 = MockDependencyFactory(value: 'Dependency 2'); - await mockDependency1.init(); - await mockDependency2.init(); + // Строим виджет с DependencyScope + final mockDependency1 = await factory1.create(); + final mockDependency2 = await factory2.create(); var a = true; @@ -116,23 +113,22 @@ void main() { home: StatefulBuilder( builder: (context, setState) => DependencyProvider( - dependency: a ? mockDependency1 : mockDependency2, - child: Builder( - builder: (context) { - final dependency = - DependencyProvider.of( - context, - listen: true); - return GestureDetector( - onTap: () { - setState(() { - a = !a; - }); - }, - child: Text(dependency.value), // Текущая зависимость - ); - }, - )), + dependency: a ? mockDependency1 : mockDependency2, + child: Builder( + builder: (context) { + final dependency = + DependencyProvider.of(context, listen: true); + return GestureDetector( + onTap: () { + setState(() { + a = !a; + }); + }, + child: Text(dependency.value), // Текущая зависимость + ); + }, + ), + ), ), ), ); diff --git a/test/dependency_scope_test.dart b/test/dependency_scope_test.dart index c5343a4..88ab996 100644 --- a/test/dependency_scope_test.dart +++ b/test/dependency_scope_test.dart @@ -4,26 +4,32 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; // Фейковая реализация DependencyContainer -class MockDependencyContainer extends Mock - implements DependencyContainer {} +class MockDependencyContainer extends DependencyContainer {} -class MockExceptionDependencyContainer extends Mock - implements DependencyContainer {} +class MockExceptionDependencyContainer extends DependencyContainer {} + +class MockDependencyFactory extends DependencyFactory { + @override + Future create() async => MockDependencyContainer(); +} + +class MockExceptionDependencyFactory + extends DependencyFactory { + @override + Future create() async { + throw Exception(); + } +} void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('DependencyScope Tests', () { testWidgets('renders placeholder when initializing', (tester) async { - final mockDependency = MockDependencyContainer(); - - when(mockDependency.init).thenAnswer((_) async {}); - when(mockDependency.inject).thenAnswer((_) async {}); - // Строим виджет с placeholder await tester.pumpWidget( - DependencyScope( - dependency: mockDependency, + DependencyScope( + factory: MockDependencyFactory(), builder: (context) => Container(), placeholder: const CircularProgressIndicator(), ), @@ -40,17 +46,12 @@ void main() { }); testWidgets('renders errorBuilder when init fails', (tester) async { - final mockDependency = MockExceptionDependencyContainer(); - - when(mockDependency.inject).thenAnswer((_) async { - throw Exception(); - }); - // Строим виджет с errorBuilder await tester.pumpWidget( MaterialApp( - home: DependencyScope( - dependency: mockDependency, + home: DependencyScope( + factory: MockExceptionDependencyFactory(), builder: (context) => Container(), errorBuilder: (context) => const Text('Error: Initialization error'), @@ -66,17 +67,11 @@ void main() { }); testWidgets('renders errorBuilder when init fails', (tester) async { - final mockDependency = MockDependencyContainer(); - - when(mockDependency.inject).thenAnswer((_) async { - throw Exception(); - }); - // Строим виджет с errorBuilder await tester.pumpWidget( MaterialApp( - home: DependencyScope( - dependency: mockDependency, + home: DependencyScope( + factory: MockExceptionDependencyFactory(), builder: (context) => Container(), ), ), @@ -88,17 +83,10 @@ void main() { }); testWidgets('calls dispose when the widget is disposed', (tester) async { - final mockDependency = MockDependencyContainer(); - - when(mockDependency.init).thenAnswer((_) async {}); - when(mockDependency.inject).thenAnswer((_) async {}); - - when(mockDependency.dispose).thenAnswer((_) async {}); - // Строим виджет await tester.pumpWidget( - DependencyScope( - dependency: mockDependency, + DependencyScope( + factory: MockDependencyFactory(), builder: (context) => Container(), ), ); @@ -106,25 +94,17 @@ void main() { // Ожидаем инициализацию await tester.pumpAndSettle(); - verifyNever(mockDependency.dispose); - await tester.pumpWidget(Container()); - - // Проверяем, что dispose будет вызван - verify(mockDependency.dispose).called(1); }); testWidgets('re-initializes when injection changes', (tester) async { final mockDependency1 = MockDependencyContainer(); final mockDependency2 = MockDependencyContainer(); - when(mockDependency1.inject).thenAnswer((_) async {}); - when(mockDependency2.inject).thenAnswer((_) async {}); - // Строим первый виджет await tester.pumpWidget( - DependencyScope( - dependency: mockDependency1, + DependencyScope( + factory: MockDependencyFactory(), builder: (context) => Container(), ), ); @@ -134,8 +114,8 @@ void main() { // Строим новый виджет с другой зависимостью await tester.pumpWidget( - DependencyScope( - dependency: mockDependency2, + DependencyScope( + factory: MockDependencyFactory(), builder: (context) => Container(), ), ); From e2f73fce72a71be21e55cfb181006527f9582084 Mon Sep 17 00:00:00 2001 From: Alexander Bangert Date: Sun, 8 Dec 2024 02:56:37 +0500 Subject: [PATCH 2/7] change doc --- MIGRATION.md | 16 +++++++++++++++- README.md | 6 ------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index f96d6d1..a7f5fc7 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,6 +1,10 @@ +# Migration +--- + ## [v4 to v5](#from-version-4-to-version-5) -## [v4 to v5](#from-version-3-to-version-4) +## [v3 to v4](#from-version-3-to-version-4) +--- ## From Version 4 to Version 5 @@ -86,6 +90,8 @@ context.depend(); context.dependMaybe(); ``` +--- + ## From Version 3 to Version 4 ### Version 3: @@ -149,3 +155,11 @@ DependencyProvider.maybeOf(context); context.depend(); context.dependMaybe(); ``` + + +#### Key Differences: +- `InjectionScope` → `DependencyScope` +- `Injection` → `DependencyContainer` +- `InjectionScope` → `DependencyProvider` + +--- diff --git a/README.md b/README.md index b25f03c..b037ea4 100644 --- a/README.md +++ b/README.md @@ -155,12 +155,6 @@ DependencyScope( [link to migrate versions](MIGRATION.md) -#### Key Differences: -- `InjectionScope` → `DependencyScope` -- `Injection` → `DependencyContainer` -- `InjectionScope` → `DependencyProvider` - ---- ## Code Coverage From e91596dfeae29b51452fa7e8d81959e7c0e0fc23 Mon Sep 17 00:00:00 2001 From: Alexander Bangert Date: Sun, 8 Dec 2024 02:59:06 +0500 Subject: [PATCH 3/7] change version --- MIGRATION.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index a7f5fc7..bbeb68d 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -8,7 +8,7 @@ ## From Version 4 to Version 5 -### Version 4: +### Version 5: ```dart class RootFactory extends DependencyFactory { @@ -53,7 +53,7 @@ DependencyProvider( -### Version 5: +### Version 4: ```dart DependencyScope( From e94757cbdfc17fae19b4e6fd3fe3fd7b0d36c82a Mon Sep 17 00:00:00 2001 From: Alexander Bangert Date: Sun, 8 Dec 2024 03:02:58 +0500 Subject: [PATCH 4/7] comments to factory --- lib/src/dependency_factory.dart | 44 ++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/lib/src/dependency_factory.dart b/lib/src/dependency_factory.dart index 4714acc..57b5e51 100644 --- a/lib/src/dependency_factory.dart +++ b/lib/src/dependency_factory.dart @@ -1,5 +1,47 @@ import 'package:depend/depend.dart'; +/// An abstract class representing a factory responsible for creating instances of a specific +/// type of [DependencyContainer]. +/// +/// The [DependencyFactory] is designed to define a blueprint for creating dependency containers, +/// which are typically used for managing application dependencies in a structured and reusable way. +/// +/// Type Parameter: +/// - [T]: A subtype of [DependencyContainer], ensuring that only valid dependency containers +/// are created by this factory. +/// +/// Usage: +/// - Extend this class and implement the [create] method to define how to construct +/// the specific dependency container. +/// +/// Example: +/// ```dart +/// class MyDependencyContainer extends DependencyContainer { +/// final String value; +/// MyDependencyContainer(this.value); +/// } +/// +/// class MyDependencyFactory extends DependencyFactory { +/// @override +/// Future create() async { +/// return MyDependencyContainer('Initialized Dependency'); +/// } +/// } +/// +/// void main() async { +/// final factory = MyDependencyFactory(); +/// final container = await factory.create(); +/// print(container.value); // Outputs: Initialized Dependency +/// } +/// ``` abstract class DependencyFactory { + /// Asynchronously creates an instance of the dependency container. + /// + /// This method must be implemented by concrete subclasses to define the logic + /// for constructing the dependency container. This could include any initialization, + /// such as creating services, loading configurations, or establishing connections. + /// + /// Returns: + /// - A [Future] that resolves to an instance of [T], the concrete dependency container. Future create(); -} +} \ No newline at end of file From c0df3cc4155558de0029c1b87570ec35b6f9247c Mon Sep 17 00:00:00 2001 From: Alexander Bangert Date: Sun, 8 Dec 2024 03:11:44 +0500 Subject: [PATCH 5/7] readme --- README.md | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b037ea4..2a65e3c 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,20 @@ Here’s the translated and updated `README.md` for the `depend` library: ## Table of Contents +1. [Depend](#depend) +2. [Features 🚀](#features-) +3. [Installation](#installation) +4. [Usage Examples](#usage-examples) + - [Example 1: Simple Initialization](#example-1-simple-initialization) + - [Step 1: Define the Dependency](#step-1-define-the-dependency) + - [Step 2: Define the DependencyFactory](#step-2-define-the-dependencyfactory) + - [Step 3: Use `DependencyScope`](#step-3-use-dependencyscope) + - [Step 4: Access the Dependency in a Widget](#step-4-access-the-dependency-in-a-widget) + - [Example 2: `DependencyProvider`](#example-2-dependencyprovider) + - [Example 3: `DependencyScope`](#example-3-dependencyscope) +5. [Migration Guide](#migration-guide) +6. [Code Coverage](#code-coverage) + --- ## Installation @@ -124,20 +138,28 @@ class MyWidget extends StatelessWidget { --- -### Example 2: +### Example 2: `DependencyProvider` ```dart final RootDependency dep = await RootFactory().create(); DependencyProvider( dependency: dep, - builder: (context) => YourWidget(); + builder: () => YourWidget(); // or child: YourWidget() ) + +class YourWidget extends StatelessWidget { + @override + Widget build(BuildContext) { + root = DependencyProvider.of(context); + ... + } +} ``` -### Example 3: DependencyScope +### Example 3: `DependencyScope` ```dart DependencyScope( From 8899aab8ec0d9586672b7d3365b5aa326277b484 Mon Sep 17 00:00:00 2001 From: Alexander Bangert Date: Sun, 8 Dec 2024 03:15:29 +0500 Subject: [PATCH 6/7] 5.0.0-dev --- CHANGELOG.md | 4 ++++ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05f7992..b53d860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.0.0-dev + +* DependencyFactory for initialize DependencyContainer + ## 4.0.0 * Big Refactoring diff --git a/pubspec.yaml b/pubspec.yaml index f4873c4..e3ed935 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: depend description: "depend simplifies dependency management in Flutter apps, providing easy initialization and access to services across the widget tree." -version: 4.0.0 +version: 5.0.0-dev homepage: https://www.contributors.info/repository/depend From 5525e1054769321b3c7515fb2e0bc34179506264 Mon Sep 17 00:00:00 2001 From: Alexander Bangert Date: Sun, 8 Dec 2024 03:29:08 +0500 Subject: [PATCH 7/7] documentation and fix bug --- analysis_options.yaml | 2 +- coverage/lcov.info | 48 ++++++++++++++--------------- example/pubspec.lock | 2 +- lib/src/dependency_container.dart | 8 +---- lib/src/dependency_factory.dart | 6 ++-- lib/src/dependency_scope.dart | 25 ++++++++++++--- test/dependency_container_test.dart | 3 -- test/dependency_provider_test.dart | 3 +- test/dependency_scope_test.dart | 7 ++--- 9 files changed, 54 insertions(+), 50 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 667a55c..5db57b2 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -191,7 +191,7 @@ linter: avoid_catches_without_on_clauses: false avoid_catching_errors: true use_to_and_as_if_applicable: true - one_member_abstracts: true + one_member_abstracts: false avoid_classes_with_only_static_members: true prefer_mixin: true use_setters_to_change_properties: true diff --git a/coverage/lcov.info b/coverage/lcov.info index 2cdcebb..c53d2a5 100644 --- a/coverage/lcov.info +++ b/coverage/lcov.info @@ -1,5 +1,5 @@ SF:lib/src/dependency_container.dart -DA:48,2 +DA:42,2 LF:1 LH:1 end_of_record @@ -29,35 +29,35 @@ LH:13 end_of_record SF:lib/src/dependency_scope.dart DA:37,1 -DA:58,1 -DA:59,1 -DA:66,1 -DA:68,1 -DA:71,1 DA:73,1 -DA:76,4 -DA:77,3 +DA:74,1 DA:81,1 -DA:84,3 -DA:85,1 +DA:83,1 +DA:86,1 DA:88,1 -DA:89,1 -DA:90,3 -DA:91,1 -DA:92,1 -DA:94,4 -DA:95,2 -DA:98,1 -DA:99,2 -DA:102,1 +DA:91,4 +DA:92,3 +DA:96,1 +DA:99,3 +DA:100,1 +DA:103,1 DA:104,1 -DA:105,1 +DA:105,3 DA:106,1 -DA:107,2 +DA:107,1 +DA:109,4 DA:110,2 -DA:111,1 -DA:112,1 -DA:113,2 +DA:113,1 +DA:114,2 +DA:117,1 +DA:119,1 +DA:120,1 +DA:121,1 +DA:122,2 +DA:125,2 +DA:126,1 +DA:127,1 +DA:128,2 LF:30 LH:30 end_of_record diff --git a/example/pubspec.lock b/example/pubspec.lock index 1a0e718..360aa0e 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -63,7 +63,7 @@ packages: path: ".." relative: true source: path - version: "4.0.0" + version: "5.0.0-dev" fake_async: dependency: transitive description: diff --git a/lib/src/dependency_container.dart b/lib/src/dependency_container.dart index afc4e38..aed58c6 100644 --- a/lib/src/dependency_container.dart +++ b/lib/src/dependency_container.dart @@ -3,8 +3,6 @@ /// in a hierarchical and structured way. /// /// The [DependencyContainer] class provides mechanisms for: -/// - Initializing dependencies via the [init] method. -/// - Accessing a parent dependency container for hierarchical setups. /// - Managing the lifecycle of dependencies, including cleanup with [dispose]. /// /// ### Usage @@ -13,11 +11,7 @@ /// initialization and cleanup logic: /// /// ```dart -/// class MyDependencyContainer extends DependencyContainer { -/// @override -/// Future init() async { -/// // Initialize your dependencies here -/// } +/// class MyDependencyContainer extends DependencyContainer { /// /// @override /// void dispose() { diff --git a/lib/src/dependency_factory.dart b/lib/src/dependency_factory.dart index 57b5e51..0aee9d2 100644 --- a/lib/src/dependency_factory.dart +++ b/lib/src/dependency_factory.dart @@ -2,7 +2,7 @@ import 'package:depend/depend.dart'; /// An abstract class representing a factory responsible for creating instances of a specific /// type of [DependencyContainer]. -/// +/// /// The [DependencyFactory] is designed to define a blueprint for creating dependency containers, /// which are typically used for managing application dependencies in a structured and reusable way. /// @@ -36,7 +36,7 @@ import 'package:depend/depend.dart'; /// ``` abstract class DependencyFactory { /// Asynchronously creates an instance of the dependency container. - /// + /// /// This method must be implemented by concrete subclasses to define the logic /// for constructing the dependency container. This could include any initialization, /// such as creating services, loading configurations, or establishing connections. @@ -44,4 +44,4 @@ abstract class DependencyFactory { /// Returns: /// - A [Future] that resolves to an instance of [T], the concrete dependency container. Future create(); -} \ No newline at end of file +} diff --git a/lib/src/dependency_scope.dart b/lib/src/dependency_scope.dart index cc2b4db..85679d7 100644 --- a/lib/src/dependency_scope.dart +++ b/lib/src/dependency_scope.dart @@ -4,7 +4,7 @@ import 'package:flutter/widgets.dart'; /// A widget that initializes and provides a [DependencyContainer] to its subtree. /// /// [DependencyScope] manages the lifecycle of the dependency by: -/// - Initializing it using the `inject` method. +/// - Initializing it using the `factory`'s [DependencyFactory.create] method. /// - Disposing of it when it is no longer needed or replaced. /// /// This widget is useful for scoping dependencies to a specific portion of the widget tree. @@ -12,8 +12,8 @@ import 'package:flutter/widgets.dart'; /// ### Example /// /// ```dart -/// DependencyScope( -/// dependency: MyDependency(), +/// DependencyScope( +/// factory: MyDependencyFactory(), /// placeholder: CircularProgressIndicator(), /// errorBuilder: (error) => Text('Error: $error'), /// builder: (context) { @@ -26,8 +26,8 @@ class DependencyScope> extends StatefulWidget { /// Creates a [DependencyScope] widget. /// - /// - The [dependency] parameter specifies the [DependencyContainer] instance - /// to be managed and provided to the subtree. + /// - The [factory] parameter specifies a [DependencyFactory] that will be used + /// to asynchronously create the [DependencyContainer] instance. /// - The [builder] parameter is a function that builds the widget tree once /// the dependency has been initialized. /// - The optional [placeholder] is displayed while the dependency is being @@ -42,6 +42,21 @@ class DependencyScope { + /// @override + /// Future create() async { + /// return MyDependency(await fetchConfig()); + /// } + /// } + /// ``` final F factory; /// A builder function that constructs the widget tree once the dependency diff --git a/test/dependency_container_test.dart b/test/dependency_container_test.dart index c368052..ae043c8 100644 --- a/test/dependency_container_test.dart +++ b/test/dependency_container_test.dart @@ -10,12 +10,10 @@ class TestDependencyContainer extends DependencyContainer { disposed = true; super.dispose(); } - } void main() { group('DependencyContainer', () { - test('should call dispose and set disposed to true', () { final container = TestDependencyContainer(); @@ -25,6 +23,5 @@ void main() { expect(container.disposed, isTrue); }); - }); } diff --git a/test/dependency_provider_test.dart b/test/dependency_provider_test.dart index 537a11d..ff198b8 100644 --- a/test/dependency_provider_test.dart +++ b/test/dependency_provider_test.dart @@ -117,7 +117,8 @@ void main() { child: Builder( builder: (context) { final dependency = - DependencyProvider.of(context, listen: true); + DependencyProvider.of(context, + listen: true); return GestureDetector( onTap: () { setState(() { diff --git a/test/dependency_scope_test.dart b/test/dependency_scope_test.dart index 88ab996..0ebc9ce 100644 --- a/test/dependency_scope_test.dart +++ b/test/dependency_scope_test.dart @@ -1,7 +1,6 @@ import 'package:depend/depend.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; // Фейковая реализация DependencyContainer class MockDependencyContainer extends DependencyContainer {} @@ -70,7 +69,8 @@ void main() { // Строим виджет с errorBuilder await tester.pumpWidget( MaterialApp( - home: DependencyScope( + home: DependencyScope( factory: MockExceptionDependencyFactory(), builder: (context) => Container(), ), @@ -98,9 +98,6 @@ void main() { }); testWidgets('re-initializes when injection changes', (tester) async { - final mockDependency1 = MockDependencyContainer(); - final mockDependency2 = MockDependencyContainer(); - // Строим первый виджет await tester.pumpWidget( DependencyScope(