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/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..bbeb68d --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,165 @@ +# Migration +--- + +## [v4 to v5](#from-version-4-to-version-5) +## [v3 to v4](#from-version-3-to-version-4) + +--- + +## From Version 4 to Version 5 + +### Version 5: + +```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 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(); +``` + +--- + +## 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(); +``` + + +#### Key Differences: +- `InjectionScope` → `DependencyScope` +- `Injection` → `DependencyContainer` +- `InjectionScope` → `DependencyProvider` + +--- diff --git a/README.md b/README.md index f9a15c7..2a65e3c 100644 --- a/README.md +++ b/README.md @@ -24,14 +24,19 @@ 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) +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) --- @@ -62,12 +67,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 +78,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 +110,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,145 +138,45 @@ class MyWidget extends StatelessWidget { --- -### Example 2: Parent Dependencies - -#### Step 1: Create the Parent Dependency +### Example 2: `DependencyProvider` ```dart -class RootDependency extends DependencyContainer { - late final ApiService apiService; +final RootDependency dep = await RootFactory().create(); - @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}); +DependencyProvider( + dependency: dep, + builder: () => YourWidget(); + // or + child: YourWidget() +) +class YourWidget extends StatelessWidget { @override - Future init() async { - authRepository = AuthRepository( - apiService: parent.apiService, - ); + Widget build(BuildContext) { + root = DependencyProvider.of(context); + ... } } ``` -#### 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(), - ), - ), - ); -} -``` - ---- - -### Example 3: DependencyScope +### 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, - ); - } - - void dispose() { - // authRepository.dispose(); - } -} -``` +## Migration Guide -```dart -DependencyProvider.of(context); -DependencyProvider.maybeOf(context); -// or -context.depend(); -context.dependMaybe(); -``` +[link to migrate versions](MIGRATION.md) -#### Key Differences: -- `InjectionScope` → `DependencyScope` -- `Injection` → `DependencyContainer` -- `InjectionScope` → `DependencyProvider` ---- ## Code Coverage 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 6c99493..c53d2a5 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:42,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:71,1 -DA:72,2 -DA:75,1 -DA:77,1 -DA:80,4 -DA:81,2 -DA:82,2 +DA:37,1 +DA:73,1 +DA:74,1 +DA:81,1 +DA:83,1 DA:86,1 -DA:89,3 -DA:90,1 -DA:94,4 +DA:88,1 +DA:91,4 +DA:92,3 DA:96,1 -DA:97,1 -DA:98,1 -DA:99,1 +DA:99,3 DA:100,1 -DA:102,4 -DA:103,2 +DA:103,1 +DA:104,1 +DA:105,3 DA:106,1 -DA:108,1 -DA:109,1 -DA:110,1 -DA:111,2 +DA:107,1 +DA:109,4 +DA:110,2 +DA:113,1 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: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/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..360aa0e 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: "5.0.0-dev" 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..aed58c6 100644 --- a/lib/src/dependency_container.dart +++ b/lib/src/dependency_container.dart @@ -1,13 +1,8 @@ -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. /// /// 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 @@ -16,11 +11,7 @@ import 'package:flutter/foundation.dart'; /// initialization and cleanup logic: /// /// ```dart -/// class MyDependencyContainer extends DependencyContainer { -/// @override -/// Future init() async { -/// // Initialize your dependencies here -/// } +/// class MyDependencyContainer extends DependencyContainer { /// /// @override /// void dispose() { @@ -31,77 +22,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..0aee9d2 --- /dev/null +++ b/lib/src/dependency_factory.dart @@ -0,0 +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(); +} 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..85679d7 100644 --- a/lib/src/dependency_scope.dart +++ b/lib/src/dependency_scope.dart @@ -1,12 +1,10 @@ -import 'dart:async'; - import 'package:depend/depend.dart'; 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. @@ -14,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) { @@ -24,12 +22,12 @@ import 'package:flutter/widgets.dart'; /// }, /// ); /// ``` -class DependencyScope> - extends StatefulWidget { +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 @@ -37,15 +35,29 @@ 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; + /// A factory that provides the logic to create the dependency asynchronously. + /// + /// This factory allows the [DependencyScope] to initialize the [DependencyContainer] + /// dynamically. If the [factory] changes during the widget's lifecycle, the + /// old dependency will be disposed, and a new one will be created. + /// + /// Example: + /// ```dart + /// class MyDependencyFactory extends DependencyFactory { + /// @override + /// Future create() async { + /// return MyDependency(await fetchConfig()); + /// } + /// } + /// ``` + final F factory; /// A builder function that constructs the widget tree once the dependency /// has been initialized. @@ -59,43 +71,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 +110,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 +123,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/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 diff --git a/test/dependency_container_test.dart b/test/dependency_container_test.dart index be99d86..ae043c8 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 @@ -12,36 +10,10 @@ class TestDependencyContainer disposed = true; 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(); @@ -51,34 +23,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..ff198b8 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,23 @@ 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..0ebc9ce 100644 --- a/test/dependency_scope_test.dart +++ b/test/dependency_scope_test.dart @@ -1,29 +1,34 @@ 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 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 +45,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 +66,12 @@ 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,14 @@ 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 +111,8 @@ void main() { // Строим новый виджет с другой зависимостью await tester.pumpWidget( - DependencyScope( - dependency: mockDependency2, + DependencyScope( + factory: MockDependencyFactory(), builder: (context) => Container(), ), );