From b7008aefabf72bc31eaa7161187b5a4742c8afd1 Mon Sep 17 00:00:00 2001 From: Alexander Bangert Date: Tue, 3 Dec 2024 14:54:38 +0500 Subject: [PATCH 1/3] provider --- coverage/lcov.info | 107 +++++---- example/lib/main.dart | 16 +- example/pubspec.lock | 74 +++--- lib/depend.dart | 8 +- lib/src/context_extension.dart | 10 + ...jection.dart => dependency_container.dart} | 21 +- lib/src/dependency_provider.dart | 88 ++++++++ lib/src/dependency_provider2.dart | 84 +++++++ lib/src/dependency_scope.dart | 75 +++++++ lib/src/injection_exception.dart | 25 ++- lib/src/injection_scope.dart | 174 -------------- pubspec.yaml | 4 + test/dependency_container_test.dart | 71 ++++++ test/dependency_library_test.dart | 212 ------------------ test/dependency_provider_test.dart | 152 ++++++++++--- test/dependency_scope_test.dart | 146 ++++++++++++ test/test.dart | 7 +- 17 files changed, 757 insertions(+), 517 deletions(-) create mode 100644 lib/src/context_extension.dart rename lib/src/{injection.dart => dependency_container.dart} (81%) create mode 100644 lib/src/dependency_provider.dart create mode 100644 lib/src/dependency_provider2.dart create mode 100644 lib/src/dependency_scope.dart delete mode 100644 lib/src/injection_scope.dart create mode 100644 test/dependency_container_test.dart delete mode 100644 test/dependency_library_test.dart create mode 100644 test/dependency_scope_test.dart diff --git a/coverage/lcov.info b/coverage/lcov.info index bba4f34..71b0958 100644 --- a/coverage/lcov.info +++ b/coverage/lcov.info @@ -1,57 +1,72 @@ -SF:lib/src/injection.dart -DA:31,2 +SF:lib/src/dependency_container.dart +DA:31,1 DA:44,1 DA:46,1 -DA:47,3 +DA:47,4 DA:49,1 DA:92,1 LF:6 LH:6 end_of_record -SF:lib/src/injection_exception.dart -DA:22,1 -LF:1 -LH:1 +SF:lib/src/context_extension.dart +DA:5,0 +DA:7,0 +LF:2 +LH:0 end_of_record -SF:lib/src/injection_scope.dart +SF:lib/src/dependency_provider.dart +DA:16,2 +DA:26,1 +DA:32,1 +DA:33,1 +DA:35,1 +DA:36,1 +DA:39,1 +DA:42,1 +DA:46,1 DA:48,1 -DA:54,4 -DA:55,3 +DA:52,1 +DA:55,1 +DA:56,3 +LF:13 +LH:13 +end_of_record +SF:lib/src/dependency_scope.dart +DA:9,2 +DA:22,2 +DA:24,2 +DA:31,2 +DA:33,2 +DA:34,4 +DA:37,2 +DA:39,2 +DA:41,8 +DA:42,4 +DA:43,4 +DA:47,2 +DA:49,6 +DA:50,2 +DA:53,8 +DA:55,2 DA:56,2 -DA:57,3 -DA:88,1 -DA:94,1 -DA:95,1 -DA:97,1 -DA:98,1 -DA:116,1 -DA:120,2 -DA:122,2 -DA:123,1 -DA:125,1 -DA:126,1 -DA:127,2 -DA:128,1 -DA:129,1 -DA:130,2 -DA:132,1 -DA:133,1 -DA:134,1 -DA:135,1 -DA:136,2 -DA:137,2 -DA:138,1 -DA:139,1 -DA:145,1 -DA:147,3 -DA:151,1 -DA:160,1 -DA:161,1 -DA:166,1 -DA:168,3 -DA:169,1 -DA:172,1 -DA:173,2 -LF:38 -LH:38 +DA:57,4 +DA:58,2 +DA:59,2 +DA:60,4 +DA:61,2 +DA:64,2 +DA:65,12 +DA:66,4 +DA:67,4 +DA:68,2 +DA:69,4 +LF:28 +LH:28 +end_of_record +SF:lib/src/injection_exception.dart +DA:26,1 +DA:43,1 +DA:44,5 +LF:3 +LH:3 end_of_record diff --git a/example/lib/main.dart b/example/lib/main.dart index bfcdda5..aea682a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -3,7 +3,7 @@ import 'package:example/src/default_bloc.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class RootInjection extends Injection { +class RootInjection extends DependencyContainer { late final ApiService apiService; @override @@ -13,7 +13,7 @@ class RootInjection extends Injection { } -class ModuleInjection extends Injection { +class ModuleInjection extends DependencyContainer { late final AuthRepository authRepository; ModuleInjection({required super.parent}); @@ -35,13 +35,13 @@ class ModuleInjection extends Injection { void main() { runApp( - InjectionScope( + DependencyScope( injection: RootInjection(), placeholder: const ColoredBox( color: Colors.white, child: Center(child: CircularProgressIndicator()), ), - child: const MyApp(), + builder: (context) => const MyApp(), ), ); } @@ -84,13 +84,13 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', - home: InjectionScope( + home: DependencyScope( injection: ModuleInjection( - parent: InjectionScope.of(context), + parent: DependencyProvider.of(context), ), - child: BlocProvider( + builder: (context) => BlocProvider( create: (context) => DefaultBloc( - InjectionScope.of(context).authRepository, + DependencyProvider.of(context).authRepository, ), child: const MyHomePage(), ), diff --git a/example/pubspec.lock b/example/pubspec.lock index 7973585..ee9b86f 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.18.0" cupertino_icons: dependency: "direct main" description: @@ -63,7 +63,7 @@ packages: path: ".." relative: true source: path - version: "2.0.3" + version: "3.0.0" fake_async: dependency: transitive description: @@ -98,14 +98,30 @@ packages: description: flutter source: sdk version: "0.0.0" - js: + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: dependency: transitive description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: @@ -118,26 +134,26 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.15.0" nested: dependency: transitive description: @@ -150,10 +166,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" provider: dependency: transitive description: @@ -171,26 +187,26 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -211,10 +227,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.7.2" vector_math: dependency: transitive description: @@ -223,6 +239,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" sdks: - dart: ">=3.0.0 <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/lib/depend.dart b/lib/depend.dart index feacb79..dfee772 100644 --- a/lib/depend.dart +++ b/lib/depend.dart @@ -1,5 +1,7 @@ library; -export 'src/injection.dart'; -export 'src/injection_exception.dart'; -export 'src/injection_scope.dart'; +export 'src/context_extension.dart'; +export 'src/dependency_container.dart'; +export 'src/dependency_provider.dart'; +export 'src/dependency_scope.dart'; +export 'src/injection_exception.dart'; \ No newline at end of file diff --git a/lib/src/context_extension.dart b/lib/src/context_extension.dart new file mode 100644 index 0000000..ac92593 --- /dev/null +++ b/lib/src/context_extension.dart @@ -0,0 +1,10 @@ +import 'package:depend/depend.dart'; +import 'package:flutter/cupertino.dart'; + +extension DependencyContext on BuildContext { + T depend>() => DependencyProvider.of(this); + + T? maybeDepend>() => DependencyProvider.maybeOf(this); + + +} \ No newline at end of file diff --git a/lib/src/injection.dart b/lib/src/dependency_container.dart similarity index 81% rename from lib/src/injection.dart rename to lib/src/dependency_container.dart index ed6db37..708ac85 100644 --- a/lib/src/injection.dart +++ b/lib/src/dependency_container.dart @@ -23,12 +23,12 @@ import 'package:flutter/foundation.dart'; /// } /// ``` /// {@endtemplate} -abstract class Injection { - /// Creates a new instance of [Injection]. +abstract class DependencyContainer { + /// Creates a new instance of [DependencyContainer]. /// /// The optional [parent] parameter allows you to reference a parent dependencies Injection, /// enabling hierarchical dependency management. - Injection({T? parent}) : _parent = parent; + DependencyContainer({T? parent}) : _parent = parent; final T? _parent; @@ -44,7 +44,7 @@ abstract class Injection { @nonVirtual T get parent { if (_parent == null) { - throw InjectionException('Parent in $runtimeType is not initialized'); + throw InjectionException('Parent in $runtimeType is not initialized', stackTrace: StackTrace.current); } return _parent!; } @@ -91,17 +91,4 @@ abstract class Injection { /// ``` void dispose() {} - /// Logs the initialization process of a dependency. - /// - /// This method executes the provided [callback] and logs the time taken to complete it. - /// In release mode, the [callback] is executed without logging to avoid performance overhead. - /// In debug mode, it measures the execution time and prints it using `debugPrint`. - /// - /// ### Example - /// - /// ```dart - /// await log(() async { - /// return await initializeMyDependency(); - /// }); - /// ``` } diff --git a/lib/src/dependency_provider.dart b/lib/src/dependency_provider.dart new file mode 100644 index 0000000..9ada01e --- /dev/null +++ b/lib/src/dependency_provider.dart @@ -0,0 +1,88 @@ +import 'package:depend/depend.dart'; +import 'package:flutter/widgets.dart'; + +class DependencyProvider> extends InheritedWidget { + /// {@macro dependencies_class} + /// + /// Creates a [DependencyProvider] widget. + /// + /// The [injection] parameter must not be null and is the [Injection] + /// instance that will be provided to descendants. + /// + /// The [child] parameter is the widget below this widget in the tree. + /// + /// The [placeholder] widget is displayed while the [injection] is initializing. + /// If [placeholder] is not provided, [child] is displayed immediately. + DependencyProvider({ + required this.injection, + required super.child, + super.key, + this.placeholder, + }); + + /// The instance of [Injection] to provide to the widget tree. + final T injection; + + /// An optional widget to display while the [injection] is initializing. + final Widget? placeholder; + + /// {@template dependencies_maybe_of} + /// Provides the nearest [Injection] of type [T] up the widget tree. + /// + /// Returns `null` if no such [DependencyProvider] is found. + /// + /// The [listen] parameter determines whether the context will rebuild when + /// the [DependencyProvider] updates. If [listen] is `true`, the context will + /// subscribe to changes; otherwise, it will not. + /// + /// ### Example + /// + /// ```dart + /// final injection = InjectionScope.maybeOf(context); + /// if (injection != null) { + /// // Use the injection + /// } + /// ``` + /// {@endtemplate} + static T? maybeOf>( + BuildContext context, { + bool listen = false, + }) => + listen + ? context + .dependOnInheritedWidgetOfExactType>() + ?.injection + : context + .getInheritedWidgetOfExactType>() + ?.injection; + + /// {@template dependencies_of} + /// Provides the nearest [Injection] of type [T] up the widget tree. + /// + /// Throws an [ArgumentError] if no such [DependencyProvider] is found. + /// + /// The [listen] parameter determines whether the context will rebuild when + /// the [DependencyProvider] updates. If [listen] is `true`, the context will + /// subscribe to changes; otherwise, it will not. + /// + /// ### Example + /// + /// ```dart + /// final injection = InjectionScope.of(context); + /// // Use the injection + /// ``` + /// {@endtemplate} + static T of>( + BuildContext context, { + bool listen = false, + }) => + maybeOf(context, listen: listen) ?? _notFound(); + + static Never _notFound>() => throw ArgumentError( + 'InjectionScope.of<$T>() called with a context that does not contain an $T.'); + + + @override + bool updateShouldNotify(DependencyProvider oldWidget) => + injection != oldWidget.injection; +} diff --git a/lib/src/dependency_provider2.dart b/lib/src/dependency_provider2.dart new file mode 100644 index 0000000..6272024 --- /dev/null +++ b/lib/src/dependency_provider2.dart @@ -0,0 +1,84 @@ +import 'package:depend/depend.dart'; +import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; +import 'package:provider/single_child_widget.dart'; + +class DependencyProvider> extends SingleChildStatelessWidget { + /// {@macro dependencies_class} + /// + /// Creates a [DependencyProvider] widget. + /// + /// The [injection] parameter must not be null and is the [DependencyContainer] + /// instance that will be provided to descendants. + /// + /// The [child] parameter is the widget below this widget in the tree. + /// + /// The [placeholder] widget is displayed while the [injection] is initializing. + /// If [placeholder] is not provided, [child] is displayed immediately. + const DependencyProvider({ + required this.injection, + + super.key, + this.builder, + bool lazy = true, + }) : _lazy = lazy; + + /// The instance of [DependencyContainer] to provide to the widget tree. + final T injection; + final bool _lazy; + final TransitionBuilder? builder; + + + static T? _findInjection>( + BuildContext context, { + required bool listen, + required bool throwIfNotFound, + }) { + try { + return Provider.of(context, listen: listen); + } on ProviderNotFoundException catch (e) { + if (throwIfNotFound) throw FlutterError( + ''' + BlocProvider.of() called with a context that does not contain a $T. + No ancestor could be found starting from the context that was passed to BlocProvider.of<$T>(). + + This can happen if the context you used comes from a widget above the BlocProvider. + + The context used was: $context + ''', + ); + return null; + } + } + + static T? maybeOf>( + BuildContext context, { + bool listen = false, + }) => + _findInjection(context, listen: listen, throwIfNotFound: false); + + static T of>( + BuildContext context, { + bool listen = false, + }) => + _findInjection(context, listen: listen, throwIfNotFound: true)!; + + + @override + Widget buildWithChild(BuildContext context, Widget? child) => InheritedProvider( + create: (context) => injection..init(), + dispose: (_, injection) => injection.dispose(), + lazy: _lazy, + builder: builder, + ); +} + + +class MultiDependencyProvider extends MultiProvider { + /// {@macro multi_bloc_provider} + MultiDependencyProvider({ + required List providers, + required Widget child, + Key? key, + }) : super(key: key, providers: providers, child: child); +} \ No newline at end of file diff --git a/lib/src/dependency_scope.dart b/lib/src/dependency_scope.dart new file mode 100644 index 0000000..18fe250 --- /dev/null +++ b/lib/src/dependency_scope.dart @@ -0,0 +1,75 @@ +import 'dart:async'; + +import 'package:depend/depend.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/widgets.dart'; + + +class DependencyScope> extends StatefulWidget { + const DependencyScope({ + required this.injection, + required this.builder, + this.placeholder, + this.errorBuilder, + super.key, + }); + + final T injection; + final Widget Function(BuildContext context) builder; + final Widget? placeholder; + final Widget Function(Object? error)? errorBuilder; + + @override + State> createState() => + _DependencyScopeState(); +} + +class _DependencyScopeState> + extends State> { + late Future Function() _initFuture; + + @override + void initState() { + super.initState(); + _initFuture = _initializeInit(); + } + + @override + void didUpdateWidget(covariant DependencyScope oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.injection != oldWidget.injection) { + oldWidget.injection.dispose(); + _initFuture = _initializeInit(); + } + } + + @override + void dispose() { + widget.injection.dispose(); + super.dispose(); + } + + Future Function() _initializeInit() => widget.injection.init; + + @override + Widget build(BuildContext context) => FutureBuilder( + future: _initFuture(), + builder: (context, snapshot) { + if(snapshot.hasError) { + return widget.errorBuilder?.call(snapshot.error) ?? + ErrorWidget(snapshot.error!); + } + + return switch(snapshot.connectionState) { + ConnectionState.none || ConnectionState.waiting || ConnectionState.active => widget.placeholder ?? SizedBox.shrink(), + ConnectionState.done => DependencyProvider( + injection: widget.injection, + child: Builder( + builder: widget.builder, + ), + ), + }; + }, + ); +} diff --git a/lib/src/injection_exception.dart b/lib/src/injection_exception.dart index ba3fdab..a0e13a4 100644 --- a/lib/src/injection_exception.dart +++ b/lib/src/injection_exception.dart @@ -6,20 +6,24 @@ /// message to be passed that describes the cause of the error when creating /// an instance of the exception. /// -/// It's used for scenarios where dependency injection fails or encounters -/// an error during execution. +/// It also stores the [stackTrace], which provides additional context +/// about where the exception occurred. /// /// Example: /// ```dart -/// throw InjectionException('Error initializing dependency'); +/// 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, - /// which will be stored in the exception. - InjectionException(this.message); + /// 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. @@ -28,4 +32,15 @@ class InjectionException implements Exception { /// 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/lib/src/injection_scope.dart b/lib/src/injection_scope.dart deleted file mode 100644 index 068d83b..0000000 --- a/lib/src/injection_scope.dart +++ /dev/null @@ -1,174 +0,0 @@ -import 'dart:async'; - -import 'package:depend/depend.dart'; -import 'package:flutter/widgets.dart'; - -/// {@template dependencies_class} -/// An `InheritedWidget` that provides access to a [Injection] -/// instance throughout the widget tree. -/// -/// The `InjectionScope` widget initializes the provided [injection] and ensures -/// it's available to all descendant widgets. It handles asynchronous -/// initialization and can display a [placeholder] widget while the [injection] -/// is being initialized. -/// -/// ### Example -/// -/// ```dart -/// class MyInjection extends Injection { -/// @override -/// Future init() async { -/// // Initialize your dependencies here -/// } -/// } -/// -/// void main() { -/// runApp( -/// InjectionScope( -/// injection: MyInjection(), -/// placeholder: CircularProgressIndicator(), -/// child: MyApp(), -/// ), -/// ); -/// } -/// ``` -/// {@endtemplate} -class InjectionScope> extends InheritedWidget { - /// {@macro dependencies_class} - /// - /// Creates a [InjectionScope] widget. - /// - /// The [injection] parameter must not be null and is the [Injection] - /// instance that will be provided to descendants. - /// - /// The [child] parameter is the widget below this widget in the tree. - /// - /// The [placeholder] widget is displayed while the [injection] is initializing. - /// If [placeholder] is not provided, [child] is displayed immediately. - InjectionScope({ - required this.injection, - required super.child, - super.key, - this.placeholder, - }) { - injection.init().then((val) { - completer.complete(injection); - }).catchError((Object error) { - completer.completeError(error, StackTrace.current); - }); - } - - /// A [Completer] to handle the asynchronous initialization of the [injection]. - final Completer completer = Completer(); - - /// The instance of [Injection] to provide to the widget tree. - final T injection; - - /// An optional widget to display while the [injection] is initializing. - final Widget? placeholder; - - /// {@template dependencies_maybe_of} - /// Provides the nearest [Injection] of type [T] up the widget tree. - /// - /// Returns `null` if no such [InjectionScope] is found. - /// - /// The [listen] parameter determines whether the context will rebuild when - /// the [InjectionScope] updates. If [listen] is `true`, the context will - /// subscribe to changes; otherwise, it will not. - /// - /// ### Example - /// - /// ```dart - /// final injection = InjectionScope.maybeOf(context); - /// if (injection != null) { - /// // Use the injection - /// } - /// ``` - /// {@endtemplate} - static T? maybeOf>( - BuildContext context, { - bool listen = false, - }) => - listen - ? context - .dependOnInheritedWidgetOfExactType>() - ?.injection - : context - .getInheritedWidgetOfExactType>() - ?.injection; - - /// {@template dependencies_of} - /// Provides the nearest [Injection] of type [T] up the widget tree. - /// - /// Throws an [ArgumentError] if no such [InjectionScope] is found. - /// - /// The [listen] parameter determines whether the context will rebuild when - /// the [InjectionScope] updates. If [listen] is `true`, the context will - /// subscribe to changes; otherwise, it will not. - /// - /// ### Example - /// - /// ```dart - /// final injection = InjectionScope.of(context); - /// // Use the injection - /// ``` - /// {@endtemplate} - static T of>( - BuildContext context, { - bool listen = false, - }) => - maybeOf(context, listen: listen) ?? _notFound(); - - static Never _notFound>() => throw ArgumentError( - 'InjectionScope.of<$T>() called with a context that does not contain an $T.'); - - @override - Widget get child => FutureBuilder( - future: completer.future, - builder: (context, snapshot) { - if (snapshot.hasError) { - return ErrorWidget(snapshot.error!); - } - return switch (snapshot.connectionState) { - ConnectionState.none || - ConnectionState.waiting || - ConnectionState.active => - placeholder ?? super.child, - ConnectionState.done => _DisposeDependency( - injection: injection, - child: super.child, - ) - }; - }, - ); - - @override - bool updateShouldNotify(InjectionScope oldWidget) => - injection != oldWidget.injection; -} - -class _DisposeDependency> extends StatefulWidget { - const _DisposeDependency({ - required this.child, - required this.injection, - super.key, - }); - - final T injection; - final Widget child; - - @override - State<_DisposeDependency> createState() => _DisposeDependencyState(); -} - -class _DisposeDependencyState> - extends State<_DisposeDependency> { - @override - void dispose() { - widget.injection.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) => widget.child; -} diff --git a/pubspec.yaml b/pubspec.yaml index ddec0cc..c516915 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,8 +30,12 @@ environment: dependencies: flutter: sdk: flutter + provider: ^6.1.2 + dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^3.0.0 + fake_async: ^1.3.1 + mocktail: ^1.0.4 diff --git a/test/dependency_container_test.dart b/test/dependency_container_test.dart new file mode 100644 index 0000000..4f6ac4d --- /dev/null +++ b/test/dependency_container_test.dart @@ -0,0 +1,71 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:depend/depend.dart'; + +/// Тестовая реализация DependencyContainer +class TestDependencyContainer extends DependencyContainer { + TestDependencyContainer({super.parent}); + + bool initialized = false; + bool disposed = false; + + @override + Future init() async { + initialized = true; + } + + @override + void dispose() { + disposed = true; + super.dispose(); + } +} + +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.initialized, isFalse); + + await container.init(); + + expect(container.initialized, isTrue); + }); + + test('should call dispose and set disposed to true', () { + final container = TestDependencyContainer(); + + expect(container.disposed, isFalse); + + container.dispose(); + + expect(container.disposed, isTrue); + }); + + test('take parent then parent null', () { + final container = TestDependencyContainer(); + + expect(() => container.parent, throwsA(isA())); + try { + print(container.parent); + } catch(err) { + expect(err.toString(), isA()); + } + + container.dispose(); + }); + }); +} diff --git a/test/dependency_library_test.dart b/test/dependency_library_test.dart deleted file mode 100644 index 6ecba7a..0000000 --- a/test/dependency_library_test.dart +++ /dev/null @@ -1,212 +0,0 @@ -import 'package:depend/depend.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class TestInjectionScopeLibrary extends Injection { - @override - Future init() async { - // Имитируем инициализацию - await Future.delayed(const Duration(milliseconds: 100), () {}); - } -} - -class FaultyInjectionScopeLibrary - extends Injection { - FaultyInjectionScopeLibrary(); - - @override - Future init() async { - await Future.delayed(const Duration(milliseconds: 100), () {}); - - // Выбрасываем исключение - throw Exception('Initialization failed'); - } -} - -class MockLibrary extends Injection { - MockLibrary() : super(); - - @override - Future init() async {} -} - -void main() { - group('InjectionScope Widget', () { - testWidgets('maybeOf should find the library in the widget tree', - (tester) async { - // Создаем виджет, в котором обернут наш Dependencies. - await tester.pumpWidget( - InjectionScope( - injection: MockLibrary(), - child: Builder( - builder: (context) { - // Вызываем maybeOf внутри контекста, чтобы проверить извлечение. - final library = - InjectionScope.maybeOf(context, listen: true); - expect(library, isNotNull); - return Container(); - }, - ), - ), - ); - }); - - test('updateShouldNotify returns true when libraries are different', () { - final oldLibrary = MockLibrary(); - final newLibrary = MockLibrary(); - - final oldDependencies = InjectionScope( - injection: oldLibrary, - child: const SizedBox(), - ); - - final newDependencies = InjectionScope( - injection: newLibrary, - child: const SizedBox(), - ); - - // Проверяем, что updateShouldNotify вернет true, если библиотеки разные - expect(newDependencies.updateShouldNotify(oldDependencies), isTrue); - }); - - test('updateShouldNotify returns false when libraries are the same', () { - final library = MockLibrary(); - - final oldDependencies = InjectionScope( - injection: library, - child: const SizedBox(), - ); - - final newDependencies = InjectionScope( - injection: library, - child: const SizedBox(), - ); - - // Проверяем, что updateShouldNotify вернет false, если библиотеки одинаковые - expect(newDependencies.updateShouldNotify(oldDependencies), isFalse); - }); - - testWidgets('maybeOf should return null when library is not found', - (tester) async { - // Проверяем, что if no Dependencies widget is found, maybeOf returns null. - await tester.pumpWidget( - Builder( - builder: (context) { - final library = - InjectionScope.maybeOf(context, listen: true); - expect(library, isNull); - return Container(); - }, - ), - ); - }); - - testWidgets('Предоставляет library потомкам', (tester) async { - final library = TestInjectionScopeLibrary(); - - await tester.pumpWidget( - InjectionScope( - injection: library, - child: Builder( - builder: (context) { - final retrievedLibrary = - InjectionScope.of(context); - expect(retrievedLibrary, equals(library)); - return Container(); - }, - ), - ), - const Duration(milliseconds: 200), - ); - }); - - testWidgets('Показывает placeholder во время инициализации', - (tester) async { - final library = TestInjectionScopeLibrary(); - const placeholderKey = Key('placeholder'); - - await tester.pumpWidget( - InjectionScope( - injection: library, - placeholder: Container(key: placeholderKey), - child: Container(), - ), - ); - - expect(find.byKey(placeholderKey), findsOneWidget); - - // Ждем завершения инициализации - await tester.pumpAndSettle(); - - expect(find.byKey(placeholderKey), findsNothing); - }); - - testWidgets('Child отображается после инициализации', (tester) async { - final library = TestInjectionScopeLibrary(); - const childKey = Key('child'); - - await tester.pumpWidget( - InjectionScope( - injection: library, - child: const SizedBox(key: childKey), - ), - ); - - expect(find.byKey(childKey), findsOneWidget); - - await tester.pumpAndSettle(); - - expect(find.byKey(childKey), findsOneWidget); - }); - - testWidgets('maybeOf возвращает null, если не найдено', (tester) async { - await tester.pumpWidget( - Builder( - builder: (context) { - final library = - InjectionScope.maybeOf(context); - expect(library, isNull); - return Container(); - }, - ), - ); - }); - - testWidgets('of бросает исключение, если зависимость не найдена', - (tester) async { - await tester.pumpWidget( - Builder( - builder: (context) { - expect(() => InjectionScope.of(context), - throwsArgumentError); - return Container(); - }, - ), - ); - }); - - testWidgets('Обрабатывает ошибку инициализации в методе init', - (tester) async { - final library = FaultyInjectionScopeLibrary(); - - await tester.pumpWidget( - InjectionScope( - injection: library, - placeholder: Container(key: const Key('placeholder')), - child: - Builder(builder: (context) => Container(key: const Key('child'))), - ), - ); - - expect(find.byKey(const Key('placeholder')), findsOneWidget); - expect(find.byKey(const Key('child')), findsNothing); - expect(find.byType(ErrorWidget), findsNothing); - - await tester.pumpAndSettle(); - - expect(find.byKey(const Key('placeholder')), findsNothing); - expect(find.byKey(const Key('child')), findsNothing); - expect(find.byType(ErrorWidget), findsOneWidget); - }); - }); -} diff --git a/test/dependency_provider_test.dart b/test/dependency_provider_test.dart index 1d6123b..b741640 100644 --- a/test/dependency_provider_test.dart +++ b/test/dependency_provider_test.dart @@ -1,39 +1,141 @@ -import 'package:depend/depend.dart'; // Замените на фактический путь +import 'package:depend/depend.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; -// Моковый класс для тестирования -class MockInjectionScopeLibrary extends Injection { - MockInjectionScopeLibrary({MockInjectionScopeLibrary? parent}) - : super(parent: parent); - bool initCalled = false; - - @override - Future init() async { - initCalled = true; - // Имитируем инициализацию - await Future.delayed(const Duration(milliseconds: 100), () => 1); - } +// Мок зависимости +class MockDependencyContainer extends Mock implements DependencyContainer { + late String value; } void main() { - group('InjectionScopeLibrary', () { - test('Должен вернуть родителя, когда он инициализирован', () async { - final parent = MockInjectionScopeLibrary(); - final child = MockInjectionScopeLibrary(parent: parent); - expect(child.parent, equals(parent)); + group('DependencyScope Tests', () { + testWidgets('retrieves dependency correctly', (tester) async { + // Создание контейнера с зависимостью + final mockDependency = MockDependencyContainer(); + + when(mockDependency.init).thenAnswer((_) async { + print('asdasd'); + mockDependency.value = 'Test Dependency'; + }); + + // Строим виджет с DependencyScope + await tester.pumpWidget( + MaterialApp( + home: DependencyScope( + injection: mockDependency, + builder: (context) { + final dependency = DependencyProvider.of(context); + print(dependency.value); + return Text(dependency.value); + } + ), + ), + ); + + await tester.pumpAndSettle(); + + + // Проверяем, что текст "Test Dependency" отображается + expect(find.text('Test Dependency'), findsOneWidget); }); - test('Должен бросить исключение, когда родитель равен null', () { - final library = MockInjectionScopeLibrary(); + testWidgets('throws error when dependency is not found', (tester) async { + - expect(() => library.parent, throwsException); + + // Строим виджет без DependencyScope + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (context) { + try { + DependencyProvider.of(context); + return const Text('No error'); + } catch(error) { + return ErrorWidget(error); + } + + }, + ), + ), + ); + + await tester.pumpAndSettle(); + + // Проверяем, что выброшена ошибка + expect(find.byType(ErrorWidget), findsOneWidget); }); - test('Метод init должен быть вызван', () async { - final library = MockInjectionScopeLibrary(); - await library.init(); + testWidgets('maybeOf returns null when dependency is not found', (tester) async { + // Строим виджет без DependencyScope + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (context) { + // Пытаемся получить зависимость через maybeOf, и она должна вернуть null + final dependency = DependencyProvider.maybeOf(context); + return Text(dependency?.value ?? 'No dependency'); + }, + ), + ), + ); - expect(library.initCalled, isTrue); + // Проверяем, что в тексте будет "No dependency" + expect(find.text('No dependency'), findsOneWidget); }); + + 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'; + }); + + bool a = true; + + // Создаём StatefulWidget для теста обновления + await tester.pumpWidget( + MaterialApp( + home: StatefulBuilder( + builder: (context, setState) { + return DependencyScope( + injection: a ? mockDependency1 : mockDependency2, + builder: (context) { + final dependency = DependencyProvider.of(context, listen: true); + return GestureDetector( + onTap: () { + setState(() { + a = !a; + }); + }, + child: Text(dependency.value), // Текущая зависимость + ); + }, + ); + }, + ), + ), + ); + + await tester.pumpAndSettle(); + + + // Проверяем начальное значение + expect(find.text('Dependency 1'), findsOneWidget); + + await tester.tap(find.text('Dependency 1')); + await tester.pumpAndSettle(); + + // Проверяем, что зависимость изменилась + expect(find.text('Dependency 2'), findsOneWidget); + }); + }); + } diff --git a/test/dependency_scope_test.dart b/test/dependency_scope_test.dart new file mode 100644 index 0000000..04e9fe2 --- /dev/null +++ b/test/dependency_scope_test.dart @@ -0,0 +1,146 @@ +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 MockExceptionDependencyContainer extends Mock implements DependencyContainer {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('DependencyScope Tests', () { + testWidgets('renders placeholder when initializing', (tester) async { + final mockDependency = MockDependencyContainer(); + + when(mockDependency.init).thenAnswer((_) async {}); + + // Строим виджет с placeholder + await tester.pumpWidget( + DependencyScope( + injection: mockDependency, + builder: (context) => Container(), + placeholder: CircularProgressIndicator(), + ), + ); + + // Ожидаем, что будет отображаться placeholder + expect(find.byType(CircularProgressIndicator), findsOneWidget); + + // Завершаем инициализацию + await tester.pumpAndSettle(); + + // Ожидаем, что виджет будет обновлен, и placeholder исчезнет + expect(find.byType(CircularProgressIndicator), findsNothing); + }); + + testWidgets('renders errorBuilder when init fails', (tester) async { + final mockDependency = MockExceptionDependencyContainer(); + + when(mockDependency.init).thenAnswer((_) async { + throw Exception(); + }); + + // Строим виджет с errorBuilder + await tester.pumpWidget( + MaterialApp( + home: DependencyScope( + injection: mockDependency, + builder: (context) => Container(), + errorBuilder: (error) => const Text('Error: Initialization error'), + ), + ), + ); + + await tester.pumpAndSettle(); + + // Ожидаем, что будет отображено сообщение об ошибке + await expectLater(find.text('Error: Initialization error'), findsOneWidget); + }); + + testWidgets('renders errorBuilder when init fails', (tester) async { + final mockDependency = MockDependencyContainer(); + + when(mockDependency.init).thenAnswer((_) async { + throw Exception('Exception'); + }); + + // Строим виджет с errorBuilder + await tester.pumpWidget( + MaterialApp( + home: DependencyScope( + injection: mockDependency, + builder: (context) => Container(), + ), + ), + ); + await tester.pumpAndSettle(); + + + // Ожидаем, что будет отображено сообщение об ошибке + await expectLater(find.byType(ErrorWidget), findsOneWidget); + }); + + testWidgets('calls dispose when the widget is disposed', (tester) async { + final mockDependency = MockDependencyContainer(); + + + when(mockDependency.init).thenAnswer((_) async {}); + when(mockDependency.dispose).thenAnswer((_) async {}); + + // Строим виджет + await tester.pumpWidget( + DependencyScope( + injection: mockDependency, + builder: (context) => Container(), + ), + ); + + // Ожидаем инициализацию + 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.init).thenAnswer((_) async {}); + when(mockDependency2.init).thenAnswer((_) async {}); + + + // Строим первый виджет + await tester.pumpWidget( + DependencyScope( + injection: mockDependency1, + builder: (context) => Container(), + ), + ); + + // Завершаем первую инициализацию + await tester.pumpAndSettle(); + + // Строим новый виджет с другой зависимостью + await tester.pumpWidget( + DependencyScope( + injection: mockDependency2, + builder: (context) => Container(), + ), + ); + + await tester.pumpAndSettle(); + + // Проверяем, что инициализация новой зависимости произошла + + }); + }); +} diff --git a/test/test.dart b/test/test.dart index 1186ae2..2697b1d 100644 --- a/test/test.dart +++ b/test/test.dart @@ -1,7 +1,10 @@ -import 'dependency_library_test.dart' as dependency_library_test; +import 'dependency_container_test.dart' as dependency_container_test; import 'dependency_provider_test.dart' as dependency_provider_test; +import 'dependency_scope_test.dart' as dependency_scope_test; + void main() { - dependency_library_test.main(); dependency_provider_test.main(); + dependency_scope_test.main(); + dependency_container_test.main(); } From 0f05a91417db465ef38bba130e6de78a3838b4be Mon Sep 17 00:00:00 2001 From: Alexander Bangert Date: Wed, 4 Dec 2024 15:06:24 +0500 Subject: [PATCH 2/3] provider --- CHANGELOG.md | 5 + README.md | 303 +++++++++--------- coverage/lcov.info | 129 ++++---- example/ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- example/lib/main.dart | 100 ++---- example/lib/src/{ => bloc}/default_bloc.dart | 3 +- example/lib/src/{ => bloc}/default_event.dart | 0 example/lib/src/{ => bloc}/default_state.dart | 0 example/lib/src/dependencies.dart | 32 ++ example/lib/src/services.dart | 38 +++ example/pubspec.lock | 74 ++--- lib/depend.dart | 2 +- lib/src/context_extension.dart | 38 ++- lib/src/dependency_container.dart | 100 +++--- lib/src/dependency_provider.dart | 128 +++++--- lib/src/dependency_provider2.dart | 84 ----- lib/src/dependency_scope.dart | 108 +++++-- lib/src/injection_exception.dart | 4 +- pubspec.yaml | 4 +- test/dependency_container_test.dart | 41 ++- test/dependency_provider_test.dart | 91 +++--- test/dependency_scope_test.dart | 44 +-- test/test.dart | 1 - 24 files changed, 698 insertions(+), 635 deletions(-) rename example/lib/src/{ => bloc}/default_bloc.dart (91%) rename example/lib/src/{ => bloc}/default_event.dart (100%) rename example/lib/src/{ => bloc}/default_state.dart (100%) create mode 100644 example/lib/src/dependencies.dart create mode 100644 example/lib/src/services.dart delete mode 100644 lib/src/dependency_provider2.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d5dd5e..9840549 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 4.0.0-dev + +* Big Refactoring + + ## 3.0.0 * Rename classes, all test passed, change readme diff --git a/README.md b/README.md index ee63d59..f9a15c7 100644 --- a/README.md +++ b/README.md @@ -1,181 +1,130 @@ +Here’s the translated and updated `README.md` for the `depend` library: -# depend +--- + +# Depend ![Pub Version](https://img.shields.io/pub/v/depend) ![License](https://img.shields.io/github/license/AlexHCJP/depend) ![Coverage](https://img.shields.io/codecov/c/github/contributors-company/depend) ![Stars](https://img.shields.io/github/stars/AlexHCJP/depend) -`depend` is a library for managing dependencies in Flutter applications. It provides a convenient way to initialize and access services or repositories via an `InheritedWidget`. +`depend` is a library for dependency management in Flutter applications. It provides a convenient way to initialize and access services and repositories via `InheritedWidget`. --- -## Why it Rocks 🚀 +## Features 🚀 -- Initialize dependencies before launching the app -- Access dependencies from anywhere in the widget tree -- Clean and extensible way to manage dependencies -- Easy to use and integrate with existing codebases +- **Dependency Initialization:** Prepare all dependencies before the app launches. +- **Global Access:** Access dependencies from anywhere in the widget tree. +- **Parent Dependencies Support:** Easily create nested or connected dependencies. +- **Ease of Use:** Integrate the library into existing code with minimal changes. --- -- **[dependencies](#depend)** - - **[Why it Rocks 🚀](#why-it-rocks-)** - - **[Installation](#installation)** - - **[Example Usage](#example-usage)** - - **[Example 1: Define InjectionScope](#example-1-define-injectionscope)** - - **[Step 2: Initialize InjectionScope](#step-2-initialize-injectionscope)** - - **[Step 3: Access InjectionScope with `InheritedWidget`](#step-3-access-injectionscope-with-inheritedwidget)** - - **[Example 2: Use Parent InjectionScope](#example-2-use-parent-injectionscope)** - - **[Step 1: Define Parent InjectionScope](#step-1-define-parent-injectionscope)** - - **[Migrate v2 to v3](#migrate-from-v2-to-v3)** +## 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 -Add the package to your `pubspec.yaml`: +Add the library to the `pubspec.yaml` of your project: ```yaml dependencies: depend: ^latest_version ``` -Then run: +Install the dependencies: ```bash -$ flutter pub get +flutter pub get ``` + --- -## Example Usage +## Usage Examples -### Example 1: Define InjectionScope +### Example 1: Simple Initialization -#### Step 1: Extends `Injection` +#### Step 1: Define the Dependency -Create a `Injection` that extends `Injection` and initializes your dependencies: +Create a class that extends `DependencyContainer` and initialize your dependencies: ```dart -class RootInjection extends Injection { +class RootDependency extends DependencyContainer { late final ApiService apiService; @override Future init() async { - apiService = await ApiService().init() + apiService = await ApiService().init(); + } + + void dispose() { + // apiService.dispose() } } ``` -#### Step 2: Initialize InjectionScope +#### Step 2: Use `DependencyScope` -Use `InjectionScope` to initialize your dependencies before launching the app: +Wrap your app in a `DependencyScope` to provide dependencies: ```dart void main() { runApp( - InjectionScope( - injection: RootInjection(), - placeholder: const ColoredBox( - color: Colors.white, - child: Center(child: CircularProgressIndicator()), - ), - child: const MyApp(), + DependencyScope( + dependency: RootDependency(), + placeholder: const Center(child: CircularProgressIndicator()), + builder: (BuildContext context) => const MyApp(), ), ); } ``` -#### Step 3: Access InjectionScope with `InheritedWidget` +#### Step 3: Access the Dependency in a Widget -Once initialized, dependencies can be accessed from anywhere in the widget tree using `InjectionScope.of(context).authRepository`: +You can now access the dependency using `DependencyProvider` anywhere in the widget tree: ```dart - -/// The repository for the example -final class AuthRepository { - final AuthDataSource dataSource; - - AuthRepository({required this.dataSource}); - - Future login() => dataSource.login(); - - void dispose() { - // stream.close(); - } -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - home: InjectionScope( - injection: ModuleInjection( - parent: InjectionScope.of(context), - ), - child: BlocProvider( - create: (context) => DefaultBloc( - InjectionScope.of(context).authRepository, - ), - child: const MyHomePage(), - ), - ), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key}); - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - void _login() { - context.read().add(DefaultEvent()); - } - +class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: SingleChildScrollView( - child: Column( - children: [ - BlocBuilder( - builder: (context, state) { - return Text('Login: ${state.authorized}'); - }, - ), - Builder( - builder: (context) { - return ElevatedButton( - onPressed: _login, - child: const Text('Login'), - ); - }, - ) - ], - ), - ), - ), + final apiService = DependencyProvider.of(context).apiService; + + return FutureBuilder( + future: apiService.getData(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const CircularProgressIndicator(); + } else if (snapshot.hasError) { + return Text('Error: ${snapshot.error}'); + } + + return Text('Data: ${snapshot.data}'); + }, ); } } - ``` -### Example 2: Use Parent InjectionScope +--- -#### Step 1: Define Parent InjectionScope +### Example 2: Parent Dependencies -```dart +#### Step 1: Create the Parent Dependency -class RootInjection extends Injection { +```dart +class RootDependency extends DependencyContainer { late final ApiService apiService; @override @@ -183,74 +132,76 @@ class RootInjection extends Injection { apiService = await ApiService().init(); } } +``` -class ModuleInjection extends Injection { +#### Step 2: Create the Child Dependency + +Use the parent dependency inside the child: + +```dart +class ModuleDependency extends DependencyContainer { late final AuthRepository authRepository; - ModuleInjection({required super.parent}); + ModuleDependency({required super.parent}); @override Future init() async { - // initialize dependencies authRepository = AuthRepository( - dataSource: AuthDataSource( - apiService: parent.apiService, // parent - RootInjection - ), + apiService: parent.apiService, ); } - - @override - void dispose() { - authRepository.dispose(); - } } - - - ``` -### Migrate from v2 to v3 -In version 2, dependencies were injected using `Dependencies`, but in version 3, this has been replaced by `InjectionScope`. Here's how you would migrate: +#### Step 3: Link Both Dependencies -#### v2: ```dart void main() { runApp( - Dependencies( - library: RootLibrary(), - placeholder: const ColoredBox( - color: Colors.white, - child: Center(child: CircularProgressIndicator()), + DependencyScope( + dependency: RootDependency(), + builder: (BuildContext context) => DependencyScope( + dependency: ModuleDependency( + parent: DependencyProvider.of(context), + // or + // parent: context.depend(), + ), + builder: (BuildContext context) => const MyApp(), ), - child: const MyApp(), ), ); } ``` -#### v3: +--- + +### Example 3: DependencyScope + ```dart -void main() { - runApp( - InjectionScope( - injection: RootInjection(), - placeholder: const ColoredBox( - color: Colors.white, - child: Center(child: CircularProgressIndicator()), - ), - child: const MyApp(), +DependencyScope( + dependency: RootDependency(), + builder: (BuildContext context) => Text('Inject'), + placeholder: Text('Placeholder'), + errorBuilder: (Object? error) => Text('Error'), ), - ); -} ``` -The key change is moving from `Dependencies` to `InjectionScope`, reflecting the updated structure for managing and accessing dependencies. +## Migration Guide ---- +### From Version 3 to Version 4 + +#### Version 3: -### v2: ```dart -class RootLibrary extends DependenciesLibrary { +InjectionScope( + library: RootLibrary(), + placeholder: const Center(child: CircularProgressIndicator()), + child: const YourWidget(), +); +``` + +```dart +class RootInjection extends Injection { late final ApiService apiService; @override @@ -260,20 +211,58 @@ class RootLibrary extends DependenciesLibrary { } ``` -### v3: ```dart -class RootInjection extends Injection { - late final ApiService apiService; +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 { - apiService = await ApiService().init(); + authRepository = AuthRepository( + apiService: parent.apiService, + ); + } + + void dispose() { + // authRepository.dispose(); } } ``` -The primary change here is the renaming of `RootLibrary` to `RootInjection`, aligning with the shift in naming conventions from `DependenciesLibrary` in v2 to `Injection` in v3. +```dart +DependencyProvider.of(context); +DependencyProvider.maybeOf(context); +// or +context.depend(); +context.dependMaybe(); +``` -## Codecov +#### Key Differences: +- `InjectionScope` → `DependencyScope` +- `Injection` → `DependencyContainer` +- `InjectionScope` → `DependencyProvider` + +--- + +## Code Coverage ![Codecov](https://codecov.io/gh/contributors-company/depend/graphs/sunburst.svg?token=DITZJ9E9OM) + +--- + +This version reflects the latest changes and provides clear guidance for new users. \ No newline at end of file diff --git a/coverage/lcov.info b/coverage/lcov.info index 71b0958..6c99493 100644 --- a/coverage/lcov.info +++ b/coverage/lcov.info @@ -1,72 +1,85 @@ SF:lib/src/dependency_container.dart -DA:31,1 -DA:44,1 -DA:46,1 -DA:47,4 -DA:49,1 -DA:92,1 -LF:6 -LH:6 +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 end_of_record SF:lib/src/context_extension.dart -DA:5,0 -DA:7,0 -LF:2 -LH:0 +DA:17,1 +DA:18,1 +DA:34,1 +DA:35,1 +LF:4 +LH:4 end_of_record SF:lib/src/dependency_provider.dart -DA:16,2 -DA:26,1 -DA:32,1 -DA:33,1 -DA:35,1 -DA:36,1 -DA:39,1 -DA:42,1 -DA:46,1 -DA:48,1 -DA:52,1 -DA:55,1 -DA:56,3 -LF:13 -LH:13 +DA:34,2 +DA:39,2 +DA:63,1 +DA:69,1 +DA:70,1 +DA:72,1 +DA:73,1 +DA:92,1 +DA:96,2 +DA:99,1 +DA:100,1 +DA:101,1 +DA:109,1 +DA:111,3 +LF:14 +LH:14 end_of_record SF:lib/src/dependency_scope.dart -DA:9,2 -DA:22,2 -DA:24,2 -DA:31,2 -DA:33,2 -DA:34,4 -DA:37,2 -DA:39,2 -DA:41,8 -DA:42,4 -DA:43,4 -DA:47,2 -DA:49,6 -DA:50,2 -DA:53,8 -DA:55,2 -DA:56,2 -DA:57,4 -DA:58,2 -DA:59,2 -DA:60,4 -DA:61,2 -DA:64,2 -DA:65,12 -DA:66,4 -DA:67,4 -DA:68,2 -DA:69,4 -LF:28 -LH:28 +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:86,1 +DA:89,3 +DA:90,1 +DA:94,4 +DA:96,1 +DA:97,1 +DA:98,1 +DA:99,1 +DA:100,1 +DA:102,4 +DA:103,2 +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:44,5 +DA:45,5 LF:3 LH:3 end_of_record diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index d4f964f..c01c581 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -168,7 +168,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 56473fd..bcc668f 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ init() async { - apiService = await ApiService().init(); - } - -} - -class ModuleInjection extends DependencyContainer { - late final AuthRepository authRepository; - - ModuleInjection({required super.parent}); - - @override - Future init() async { - authRepository = AuthRepository( - dataSource: AuthDataSource( - apiService: parent.apiService, - ), - ); - } - - @override - void dispose() { - authRepository.dispose(); - } -} - void main() { - runApp( - DependencyScope( - injection: RootInjection(), - placeholder: const ColoredBox( - color: Colors.white, - child: Center(child: CircularProgressIndicator()), - ), - builder: (context) => const MyApp(), - ), - ); -} - -/// The API service for the example -class ApiService { - ApiService(); - - Future init() async { - return Future.delayed(const Duration(seconds: 1), () => this); - } -} - -/// The data source for the example -class AuthDataSource { - final ApiService apiService; - - AuthDataSource({required this.apiService}); - - Future login() => Future.value('Token'); + runApp(const MyApp()); } -/// The repository for the example -final class AuthRepository { - final AuthDataSource dataSource; - - AuthRepository({required this.dataSource}); - - Future login() => dataSource.login(); - - void dispose() { - // stream.close(); - } -} class MyApp extends StatelessWidget { const MyApp({super.key}); @@ -84,15 +17,26 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', - home: DependencyScope( - injection: ModuleInjection( - parent: DependencyProvider.of(context), + home: DependencyScope( + dependency: RootInjection(), + placeholder: const ColoredBox( + color: Colors.white, + child: CupertinoActivityIndicator(), ), - builder: (context) => BlocProvider( - create: (context) => DefaultBloc( - DependencyProvider.of(context).authRepository, + builder: (context) => DependencyScope( + dependency: ModuleInjection( + parent: DependencyProvider.of(context), + ), + placeholder: const ColoredBox( + color: Colors.white, + child: CupertinoActivityIndicator(), + ), + builder: (context) => BlocProvider( + create: (context) => DefaultBloc( + DependencyProvider.of(context).authRepository, + ), + child: const MyHomePage(), ), - child: const MyHomePage(), ), ), ); diff --git a/example/lib/src/default_bloc.dart b/example/lib/src/bloc/default_bloc.dart similarity index 91% rename from example/lib/src/default_bloc.dart rename to example/lib/src/bloc/default_bloc.dart index 01a2dfe..e940146 100644 --- a/example/lib/src/default_bloc.dart +++ b/example/lib/src/bloc/default_bloc.dart @@ -1,5 +1,6 @@ import 'package:bloc/bloc.dart'; -import 'package:example/main.dart'; + +import '../services.dart'; part 'default_event.dart'; part 'default_state.dart'; diff --git a/example/lib/src/default_event.dart b/example/lib/src/bloc/default_event.dart similarity index 100% rename from example/lib/src/default_event.dart rename to example/lib/src/bloc/default_event.dart diff --git a/example/lib/src/default_state.dart b/example/lib/src/bloc/default_state.dart similarity index 100% rename from example/lib/src/default_state.dart rename to example/lib/src/bloc/default_state.dart diff --git a/example/lib/src/dependencies.dart b/example/lib/src/dependencies.dart new file mode 100644 index 0000000..76d5d10 --- /dev/null +++ b/example/lib/src/dependencies.dart @@ -0,0 +1,32 @@ + +import 'package:depend/depend.dart'; +import 'package:example/src/services.dart'; + +class RootInjection extends DependencyContainer { + late final ApiService apiService; + + @override + Future init() async { + apiService = await ApiService().init(); + } +} + +class ModuleInjection extends DependencyContainer { + late final AuthRepository authRepository; + + ModuleInjection({required super.parent}); + + @override + Future init() async { + authRepository = AuthRepository( + dataSource: AuthDataSource( + apiService: parent.apiService, + ), + ); + } + + @override + void dispose() { + authRepository.dispose(); + } +} diff --git a/example/lib/src/services.dart b/example/lib/src/services.dart new file mode 100644 index 0000000..ff16acc --- /dev/null +++ b/example/lib/src/services.dart @@ -0,0 +1,38 @@ + +import 'dart:async'; + +/// The API service for the example +class ApiService { + ApiService(); + + Future init() async { + await Future.delayed(const Duration(seconds: 1), () {}); + return this; + } +} + +/// The data source for the example +class AuthDataSource { + final ApiService apiService; + + AuthDataSource({required this.apiService}); + + Future login() => Future.value('Token'); +} + +/// The repository for the example +final class AuthRepository { + final AuthDataSource dataSource; + + final StreamController _stream; + + Stream get stream => _stream.stream; + + AuthRepository({required this.dataSource}): _stream = StreamController.broadcast(); + + Future login() => dataSource.login(); + + void dispose() { + _stream.close(); + } +} \ No newline at end of file diff --git a/example/pubspec.lock b/example/pubspec.lock index ee9b86f..2c5dc1a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.1" cupertino_icons: dependency: "direct main" description: @@ -63,7 +63,7 @@ packages: path: ".." relative: true source: path - version: "3.0.0" + version: "4.0.0-dev" fake_async: dependency: transitive description: @@ -98,30 +98,14 @@ packages: description: flutter source: sdk version: "0.0.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" - url: "https://pub.dev" - source: hosted - version: "10.0.5" - leak_tracker_flutter_testing: + js: dependency: transitive description: - name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "3.0.5" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.dev" - source: hosted - version: "3.0.1" + version: "0.6.7" lints: dependency: transitive description: @@ -134,26 +118,26 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.2.0" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.9.1" nested: dependency: transitive description: @@ -166,10 +150,10 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.8.3" provider: dependency: transitive description: @@ -187,26 +171,26 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -227,10 +211,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.5.1" vector_math: dependency: transitive description: @@ -239,14 +223,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" - url: "https://pub.dev" - source: hosted - version: "14.2.5" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" diff --git a/lib/depend.dart b/lib/depend.dart index dfee772..a0b6278 100644 --- a/lib/depend.dart +++ b/lib/depend.dart @@ -4,4 +4,4 @@ export 'src/context_extension.dart'; export 'src/dependency_container.dart'; export 'src/dependency_provider.dart'; export 'src/dependency_scope.dart'; -export 'src/injection_exception.dart'; \ No newline at end of file +export 'src/injection_exception.dart'; diff --git a/lib/src/context_extension.dart b/lib/src/context_extension.dart index ac92593..284cc2b 100644 --- a/lib/src/context_extension.dart +++ b/lib/src/context_extension.dart @@ -1,10 +1,36 @@ import 'package:depend/depend.dart'; -import 'package:flutter/cupertino.dart'; +import 'package:flutter/widgets.dart'; +/// An extension on [BuildContext] that provides convenient methods +/// for accessing dependencies registered via [DependencyProvider]. extension DependencyContext on BuildContext { - T depend>() => DependencyProvider.of(this); + /// Retrieves a dependency of type [T] from the nearest [DependencyProvider] + /// in the widget tree. + /// + /// This method requires that a [DependencyProvider] of type [T] is present + /// in the widget tree. If not, it will throw an error. + /// + /// Example: + /// ```dart + /// MyDependencyContainer myDependency = context.depend(); + /// ``` + T depend>() => + DependencyProvider.of(this); - T? maybeDepend>() => DependencyProvider.maybeOf(this); - - -} \ No newline at end of file + /// Retrieves a dependency of type [T] from the nearest [DependencyProvider] + /// in the widget tree, or returns `null` if no matching [DependencyProvider] + /// is found. + /// + /// This method is useful if the dependency is optional and you want to avoid + /// throwing an error when it is not present. + /// + /// Example: + /// ```dart + /// MyDependencyContainer? myDependency = context.maybeDepend(); + /// if (myDependency != null) { + /// // Use the dependency + /// } + /// ``` + T? maybeDepend>() => + DependencyProvider.maybeOf(this); +} diff --git a/lib/src/dependency_container.dart b/lib/src/dependency_container.dart index 708ac85..52d8569 100644 --- a/lib/src/dependency_container.dart +++ b/lib/src/dependency_container.dart @@ -2,93 +2,121 @@ import 'package:depend/depend.dart'; import 'package:flutter/foundation.dart'; /// {@template dependencies_Injection} -/// An abstract class that serves as a base for managing dependencies in your application. -/// It provides a structure for initializing dependencies and accessing a parent Injection if needed. +/// An abstract class that serves as a foundation for managing dependencies +/// in a hierarchical and structured way. /// -/// The `Injection` class is designed to be extended by your own dependency Injection classes, -/// allowing you to define initialization logic and manage dependencies effectively. +/// 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]. /// -/// ### Example +/// ### Usage +/// +/// Extend this class to create your own dependency container and implement +/// initialization and cleanup logic: /// /// ```dart -/// class MyInjectionScope extends Injection { +/// class MyDependencyContainer extends DependencyContainer { /// @override /// Future init() async { /// // Initialize your dependencies here -/// await log(() async { -/// // Initialize a specific dependency -/// return await initializeMyDependency(); -/// }); +/// } +/// +/// @override +/// void dispose() { +/// // Clean up resources +/// myStreamController.close(); +/// super.dispose(); /// } /// } /// ``` /// {@endtemplate} abstract class DependencyContainer { - /// Creates a new instance of [DependencyContainer]. + /// Creates an instance of [DependencyContainer]. /// - /// The optional [parent] parameter allows you to reference a parent dependencies Injection, - /// enabling hierarchical dependency management. + /// - 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 dependencies Injection. + /// Provides access to the parent dependency container. /// - /// Throws an [Exception] if the parent is not initialized. + /// Throws an [InjectionException] if the parent is not set or initialized. /// /// ### Example - /// /// ```dart - /// final parentLibrary = parent; + /// final parentContainer = parent; /// ``` @nonVirtual T get parent { if (_parent == null) { - throw InjectionException('Parent in $runtimeType is not initialized', stackTrace: StackTrace.current); + throw InjectionException( + 'Parent in $runtimeType is not initialized', + stackTrace: StackTrace.current, + ); } return _parent!; } - /// Initializes the dependencies. + /// Tracks whether the container's initialization logic has been executed. /// - /// This method should be overridden in subclasses to implement the initialization logic - /// for your dependencies. The `@mustCallSuper` annotation indicates that if you override - /// this method, you should call `super.init()` to ensure proper initialization. + /// 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. /// - /// ### Example + /// - 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(); - /// // Your initialization code here + /// // Custom initialization logic + /// someDependency = await initializeDependency(); /// } /// ``` @mustCallSuper Future init(); - /// Cleans up resources used by the dependencies. - /// - /// This method can be overridden to release any resources, close streams, or dispose - /// of objects that were initialized in the `init` method or elsewhere in the Injection. + /// A wrapper method that ensures [init] is called only once. /// - /// The base implementation is empty, so calling `super.dispose()` is optional unless - /// overridden by subclasses to include specific cleanup logic. + /// 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(); + } + + /// Cleans up resources or dependencies managed by this container. + /// + /// Override this method to perform any necessary cleanup, such as closing + /// streams, canceling timers, or disposing objects. /// + /// The base implementation does nothing, so overriding this method is + /// optional unless cleanup is required. + /// + /// ### Example /// ```dart /// @override /// void dispose() { - /// // Close a stream controller /// myStreamController.close(); - /// - /// // Dispose of other resources - /// someDependency.dispose(); - /// /// super.dispose(); /// } /// ``` void dispose() {} - } diff --git a/lib/src/dependency_provider.dart b/lib/src/dependency_provider.dart index 9ada01e..20af52e 100644 --- a/lib/src/dependency_provider.dart +++ b/lib/src/dependency_provider.dart @@ -1,88 +1,112 @@ import 'package:depend/depend.dart'; import 'package:flutter/widgets.dart'; -class DependencyProvider> extends InheritedWidget { - /// {@macro dependencies_class} - /// +/// A widget that provides a [DependencyContainer] (or its subclass) to its subtree. +/// +/// [DependencyProvider] acts as a bridge to pass dependencies down the widget +/// tree. It allows widgets to access the provided dependency using the static +/// [of] or [maybeOf] methods. +/// +/// ### Usage +/// +/// ```dart +/// DependencyProvider( +/// injection: MyDependency(), +/// child: MyApp(), +/// ); +/// ``` +/// +/// Widgets in the subtree can access the dependency as follows: +/// +/// ```dart +/// final myDependency = DependencyProvider.of(context); +/// ``` +class DependencyProvider> + extends InheritedWidget { /// Creates a [DependencyProvider] widget. /// - /// The [injection] parameter must not be null and is the [Injection] - /// instance that will be provided to descendants. - /// - /// The [child] parameter is the widget below this widget in the tree. - /// - /// The [placeholder] widget is displayed while the [injection] is initializing. - /// If [placeholder] is not provided, [child] is displayed immediately. + /// - The [dependency] parameter is the dependency instance to provide to + /// the widget tree. It must not be `null`. + /// - Either [builder] or [child] must be provided: + /// - [builder]: A function that returns the child widget. This is useful + /// when the child requires access to the dependency during its creation. + /// - [child]: The widget below this widget in the tree. DependencyProvider({ - required this.injection, - required super.child, + required this.dependency, super.key, - this.placeholder, - }); - - /// The instance of [Injection] to provide to the widget tree. - final T injection; + Widget Function()? builder, + Widget? child, + }) : super(child: child ?? builder?.call() ?? const Offstage()); - /// An optional widget to display while the [injection] is initializing. - final Widget? placeholder; + /// The dependency instance being provided to the subtree. + final T dependency; - /// {@template dependencies_maybe_of} - /// Provides the nearest [Injection] of type [T] up the widget tree. + /// Provides the nearest [DependencyContainer] of type [T] from the widget tree. /// - /// Returns `null` if no such [DependencyProvider] is found. + /// This method returns `null` if no [DependencyProvider] of the specified + /// type is found. /// - /// The [listen] parameter determines whether the context will rebuild when - /// the [DependencyProvider] updates. If [listen] is `true`, the context will - /// subscribe to changes; otherwise, it will not. + /// - The [listen] parameter determines whether the widget should rebuild + /// when the [DependencyProvider] updates: + /// - If `true`, the context will subscribe to changes and rebuild when + /// the dependency updates. + /// - If `false`, the context will not rebuild when the dependency changes. /// /// ### Example /// /// ```dart - /// final injection = InjectionScope.maybeOf(context); - /// if (injection != null) { - /// // Use the injection + /// final myDependency = DependencyProvider.maybeOf(context); + /// if (myDependency != null) { + /// // Use the dependency /// } /// ``` - /// {@endtemplate} static T? maybeOf>( - BuildContext context, { - bool listen = false, - }) => + BuildContext context, { + bool listen = false, + }) => listen ? context - .dependOnInheritedWidgetOfExactType>() - ?.injection + .dependOnInheritedWidgetOfExactType>() + ?.dependency : context - .getInheritedWidgetOfExactType>() - ?.injection; + .getInheritedWidgetOfExactType>() + ?.dependency; - /// {@template dependencies_of} - /// Provides the nearest [Injection] of type [T] up the widget tree. + /// Provides the nearest [DependencyContainer] of type [T] from the widget tree. /// - /// Throws an [ArgumentError] if no such [DependencyProvider] is found. + /// This method throws an [ArgumentError] if no [DependencyProvider] of the + /// specified type is found. /// - /// The [listen] parameter determines whether the context will rebuild when - /// the [DependencyProvider] updates. If [listen] is `true`, the context will - /// subscribe to changes; otherwise, it will not. + /// - The [listen] parameter determines whether the widget should rebuild + /// when the [DependencyProvider] updates: + /// - If `true`, the context will subscribe to changes and rebuild when + /// the dependency updates. + /// - If `false`, the context will not rebuild when the dependency changes. /// /// ### Example /// /// ```dart - /// final injection = InjectionScope.of(context); - /// // Use the injection + /// final myDependency = DependencyProvider.of(context); + /// // Use the dependency /// ``` - /// {@endtemplate} static T of>( - BuildContext context, { - bool listen = false, - }) => + BuildContext context, { + bool listen = false, + }) => maybeOf(context, listen: listen) ?? _notFound(); - static Never _notFound>() => throw ArgumentError( - 'InjectionScope.of<$T>() called with a context that does not contain an $T.'); - + /// 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.'); + /// Determines whether widgets that depend on this [DependencyProvider] should rebuild. + /// + /// Returns `true` if the provided [dependency] has changed since the last + /// build, and `false` otherwise. + /// + /// This is used by the Flutter framework to optimize widget rebuilding. @override bool updateShouldNotify(DependencyProvider oldWidget) => - injection != oldWidget.injection; + dependency != oldWidget.dependency; } diff --git a/lib/src/dependency_provider2.dart b/lib/src/dependency_provider2.dart deleted file mode 100644 index 6272024..0000000 --- a/lib/src/dependency_provider2.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'package:depend/depend.dart'; -import 'package:flutter/widgets.dart'; -import 'package:provider/provider.dart'; -import 'package:provider/single_child_widget.dart'; - -class DependencyProvider> extends SingleChildStatelessWidget { - /// {@macro dependencies_class} - /// - /// Creates a [DependencyProvider] widget. - /// - /// The [injection] parameter must not be null and is the [DependencyContainer] - /// instance that will be provided to descendants. - /// - /// The [child] parameter is the widget below this widget in the tree. - /// - /// The [placeholder] widget is displayed while the [injection] is initializing. - /// If [placeholder] is not provided, [child] is displayed immediately. - const DependencyProvider({ - required this.injection, - - super.key, - this.builder, - bool lazy = true, - }) : _lazy = lazy; - - /// The instance of [DependencyContainer] to provide to the widget tree. - final T injection; - final bool _lazy; - final TransitionBuilder? builder; - - - static T? _findInjection>( - BuildContext context, { - required bool listen, - required bool throwIfNotFound, - }) { - try { - return Provider.of(context, listen: listen); - } on ProviderNotFoundException catch (e) { - if (throwIfNotFound) throw FlutterError( - ''' - BlocProvider.of() called with a context that does not contain a $T. - No ancestor could be found starting from the context that was passed to BlocProvider.of<$T>(). - - This can happen if the context you used comes from a widget above the BlocProvider. - - The context used was: $context - ''', - ); - return null; - } - } - - static T? maybeOf>( - BuildContext context, { - bool listen = false, - }) => - _findInjection(context, listen: listen, throwIfNotFound: false); - - static T of>( - BuildContext context, { - bool listen = false, - }) => - _findInjection(context, listen: listen, throwIfNotFound: true)!; - - - @override - Widget buildWithChild(BuildContext context, Widget? child) => InheritedProvider( - create: (context) => injection..init(), - dispose: (_, injection) => injection.dispose(), - lazy: _lazy, - builder: builder, - ); -} - - -class MultiDependencyProvider extends MultiProvider { - /// {@macro multi_bloc_provider} - MultiDependencyProvider({ - required List providers, - required Widget child, - Key? key, - }) : super(key: key, providers: providers, child: child); -} \ No newline at end of file diff --git a/lib/src/dependency_scope.dart b/lib/src/dependency_scope.dart index 18fe250..c6ea276 100644 --- a/lib/src/dependency_scope.dart +++ b/lib/src/dependency_scope.dart @@ -1,32 +1,70 @@ import 'dart:async'; import 'package:depend/depend.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/widgets.dart'; - -class DependencyScope> extends StatefulWidget { +/// 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. +/// - 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. +/// +/// ### Example +/// +/// ```dart +/// DependencyScope( +/// dependency: MyDependency(), +/// placeholder: CircularProgressIndicator(), +/// errorBuilder: (error) => Text('Error: $error'), +/// builder: (context) { +/// final dependency = DependencyProvider.of(context); +/// return MyWidget(dependency: dependency); +/// }, +/// ); +/// ``` +class DependencyScope> + extends StatefulWidget { + /// Creates a [DependencyScope] widget. + /// + /// - The [dependency] parameter specifies the [DependencyContainer] instance + /// to be managed and provided to the subtree. + /// - 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 + /// initialized. + /// - The optional [errorBuilder] is called if an error occurs during + /// initialization. const DependencyScope({ - required this.injection, + required this.dependency, required this.builder, this.placeholder, this.errorBuilder, super.key, }); - final T injection; + /// The dependency to be managed and provided. + final T dependency; + + /// A builder function that constructs the widget tree once the dependency + /// has been initialized. final Widget Function(BuildContext context) builder; + + /// A widget to display while the dependency is being initialized. final Widget? placeholder; + + /// A builder function to construct a widget if an error occurs during + /// dependency initialization. final Widget Function(Object? error)? errorBuilder; @override - State> createState() => - _DependencyScopeState(); + State> createState() => _DependencyScopeState(); } class _DependencyScopeState> extends State> { - late Future Function() _initFuture; + late Future _initFuture; @override void initState() { @@ -38,38 +76,48 @@ class _DependencyScopeState> void didUpdateWidget(covariant DependencyScope oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.injection != oldWidget.injection) { - oldWidget.injection.dispose(); + // Dispose of the old dependency and initialize the new one if it has changed. + if (widget.dependency != oldWidget.dependency) { + oldWidget.dependency.dispose(); _initFuture = _initializeInit(); } } @override void dispose() { - widget.injection.dispose(); + // Dispose of the managed dependency when the widget is removed from the tree. + widget.dependency.dispose(); super.dispose(); } - Future Function() _initializeInit() => widget.injection.init; + /// Initializes the dependency using its [inject] method. + Future _initializeInit() => widget.dependency.inject(); @override Widget build(BuildContext context) => FutureBuilder( - future: _initFuture(), - builder: (context, snapshot) { - if(snapshot.hasError) { - return widget.errorBuilder?.call(snapshot.error) ?? - ErrorWidget(snapshot.error!); - } - - return switch(snapshot.connectionState) { - ConnectionState.none || ConnectionState.waiting || ConnectionState.active => widget.placeholder ?? SizedBox.shrink(), - ConnectionState.done => DependencyProvider( - injection: widget.injection, - child: Builder( - builder: widget.builder, - ), - ), - }; - }, - ); + future: _initFuture, + builder: (context, snapshot) { + if (snapshot.hasError) { + // Display the error widget if an error occurs during initialization. + return widget.errorBuilder?.call(snapshot.error) ?? + ErrorWidget(snapshot.error!); + } + + return switch (snapshot.connectionState) { + // Show the placeholder while waiting for the dependency to initialize. + ConnectionState.none || + ConnectionState.waiting || + ConnectionState.active => + widget.placeholder ?? const SizedBox.shrink(), + + // Provide the dependency once initialization is complete. + ConnectionState.done => DependencyProvider( + dependency: widget.dependency, + child: Builder( + builder: widget.builder, + ), + ), + }; + }, + ); } diff --git a/lib/src/injection_exception.dart b/lib/src/injection_exception.dart index a0e13a4..9f2159c 100644 --- a/lib/src/injection_exception.dart +++ b/lib/src/injection_exception.dart @@ -41,6 +41,6 @@ class InjectionException implements Exception { final StackTrace? stackTrace; @override - String toString() => 'InjectionException: $message${stackTrace != null ? '\n$stackTrace' : ''}'; + String toString() => + 'InjectionException: $message${stackTrace != null ? '\n$stackTrace' : ''}'; } - diff --git a/pubspec.yaml b/pubspec.yaml index c516915..235e047 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: 3.0.0 +version: 4.0.0-dev homepage: https://www.contributors.info/repository/depend @@ -30,12 +30,10 @@ environment: dependencies: flutter: sdk: flutter - provider: ^6.1.2 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^3.0.0 - fake_async: ^1.3.1 mocktail: ^1.0.4 diff --git a/test/dependency_container_test.dart b/test/dependency_container_test.dart index 4f6ac4d..be99d86 100644 --- a/test/dependency_container_test.dart +++ b/test/dependency_container_test.dart @@ -1,23 +1,20 @@ -import 'package:flutter_test/flutter_test.dart'; import 'package:depend/depend.dart'; +import 'package:flutter_test/flutter_test.dart'; /// Тестовая реализация DependencyContainer -class TestDependencyContainer extends DependencyContainer { +class TestDependencyContainer + extends DependencyContainer { TestDependencyContainer({super.parent}); - - bool initialized = false; bool disposed = false; - @override - Future init() async { - initialized = true; - } - @override void dispose() { disposed = true; super.dispose(); } + + @override + Future init() async {} } void main() { @@ -38,11 +35,11 @@ void main() { test('should call init and set initialized to true', () async { final container = TestDependencyContainer(); - expect(container.initialized, isFalse); + expect(container.isInitialization, isFalse); - await container.init(); + await container.inject(); - expect(container.initialized, isTrue); + expect(container.isInitialization, isTrue); }); test('should call dispose and set disposed to true', () { @@ -60,12 +57,28 @@ void main() { expect(() => container.parent, throwsA(isA())); try { - print(container.parent); - } catch(err) { + 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 b741640..2687669 100644 --- a/test/dependency_provider_test.dart +++ b/test/dependency_provider_test.dart @@ -4,7 +4,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; // Мок зависимости -class MockDependencyContainer extends Mock implements DependencyContainer { +class MockDependencyContainer extends Mock + implements DependencyContainer { late String value; } @@ -13,37 +14,42 @@ void main() { testWidgets('retrieves dependency correctly', (tester) async { // Создание контейнера с зависимостью final mockDependency = MockDependencyContainer(); + final k1 = GlobalKey(); + final mkey = GlobalKey(); when(mockDependency.init).thenAnswer((_) async { - print('asdasd'); mockDependency.value = 'Test Dependency'; }); + await mockDependency.init(); + // Строим виджет с DependencyScope await tester.pumpWidget( MaterialApp( - home: DependencyScope( - injection: mockDependency, - builder: (context) { - final dependency = DependencyProvider.of(context); - print(dependency.value); - return Text(dependency.value); - } - ), + key: mkey, + home: DependencyProvider( + dependency: mockDependency, + child: Builder( + builder: (context) { + final dependency = + DependencyProvider.of(context); + return Text(key: k1, dependency.value); + }, + )), ), ); await tester.pumpAndSettle(); - // Проверяем, что текст "Test Dependency" отображается expect(find.text('Test Dependency'), findsOneWidget); + expect( + k1.currentContext!.depend(), mockDependency); + expect( + mkey.currentContext?.maybeDepend(), isNull); }); testWidgets('throws error when dependency is not found', (tester) async { - - - // Строим виджет без DependencyScope await tester.pumpWidget( MaterialApp( @@ -52,29 +58,30 @@ void main() { try { DependencyProvider.of(context); return const Text('No error'); - } catch(error) { + } catch (error) { return ErrorWidget(error); } - }, ), ), ); await tester.pumpAndSettle(); - + // Проверяем, что выброшена ошибка expect(find.byType(ErrorWidget), findsOneWidget); }); - testWidgets('maybeOf returns null when dependency is not found', (tester) async { + testWidgets('maybeOf returns null when dependency is not found', + (tester) async { // Строим виджет без DependencyScope await tester.pumpWidget( MaterialApp( home: Builder( builder: (context) { // Пытаемся получить зависимость через maybeOf, и она должна вернуть null - final dependency = DependencyProvider.maybeOf(context); + final dependency = + DependencyProvider.maybeOf(context); return Text(dependency?.value ?? 'No dependency'); }, ), @@ -85,7 +92,8 @@ void main() { expect(find.text('No dependency'), findsOneWidget); }); - testWidgets('updateShouldNotify returns true if dependency changes', (tester) async { + testWidgets('updateShouldNotify returns true if dependency changes', + (tester) async { // Строим виджет с DependencyScope final mockDependency1 = MockDependencyContainer(); final mockDependency2 = MockDependencyContainer(); @@ -97,35 +105,40 @@ void main() { mockDependency2.value = 'Dependency 2'; }); - bool a = true; + await mockDependency1.init(); + await mockDependency2.init(); + + var a = true; // Создаём StatefulWidget для теста обновления await tester.pumpWidget( MaterialApp( home: StatefulBuilder( - builder: (context, setState) { - return DependencyScope( - injection: a ? mockDependency1 : mockDependency2, - builder: (context) { - final dependency = DependencyProvider.of(context, listen: true); - return GestureDetector( - onTap: () { - setState(() { - a = !a; - }); - }, - child: Text(dependency.value), // Текущая зависимость - ); - }, - ); - }, + 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), // Текущая зависимость + ); + }, + )), ), ), ); await tester.pumpAndSettle(); - // Проверяем начальное значение expect(find.text('Dependency 1'), findsOneWidget); @@ -135,7 +148,5 @@ void main() { // Проверяем, что зависимость изменилась expect(find.text('Dependency 2'), findsOneWidget); }); - }); - } diff --git a/test/dependency_scope_test.dart b/test/dependency_scope_test.dart index 04e9fe2..c5343a4 100644 --- a/test/dependency_scope_test.dart +++ b/test/dependency_scope_test.dart @@ -4,9 +4,11 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; // Фейковая реализация DependencyContainer -class MockDependencyContainer extends Mock implements DependencyContainer {} +class MockDependencyContainer extends Mock + implements DependencyContainer {} -class MockExceptionDependencyContainer extends Mock implements DependencyContainer {} +class MockExceptionDependencyContainer extends Mock + implements DependencyContainer {} void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -16,13 +18,14 @@ void main() { final mockDependency = MockDependencyContainer(); when(mockDependency.init).thenAnswer((_) async {}); + when(mockDependency.inject).thenAnswer((_) async {}); // Строим виджет с placeholder await tester.pumpWidget( DependencyScope( - injection: mockDependency, + dependency: mockDependency, builder: (context) => Container(), - placeholder: CircularProgressIndicator(), + placeholder: const CircularProgressIndicator(), ), ); @@ -39,7 +42,7 @@ void main() { testWidgets('renders errorBuilder when init fails', (tester) async { final mockDependency = MockExceptionDependencyContainer(); - when(mockDependency.init).thenAnswer((_) async { + when(mockDependency.inject).thenAnswer((_) async { throw Exception(); }); @@ -47,9 +50,10 @@ void main() { await tester.pumpWidget( MaterialApp( home: DependencyScope( - injection: mockDependency, + dependency: mockDependency, builder: (context) => Container(), - errorBuilder: (error) => const Text('Error: Initialization error'), + errorBuilder: (context) => + const Text('Error: Initialization error'), ), ), ); @@ -57,28 +61,28 @@ void main() { await tester.pumpAndSettle(); // Ожидаем, что будет отображено сообщение об ошибке - await expectLater(find.text('Error: Initialization error'), findsOneWidget); + await expectLater( + find.text('Error: Initialization error'), findsOneWidget); }); testWidgets('renders errorBuilder when init fails', (tester) async { final mockDependency = MockDependencyContainer(); - when(mockDependency.init).thenAnswer((_) async { - throw Exception('Exception'); + when(mockDependency.inject).thenAnswer((_) async { + throw Exception(); }); // Строим виджет с errorBuilder await tester.pumpWidget( MaterialApp( home: DependencyScope( - injection: mockDependency, + dependency: mockDependency, builder: (context) => Container(), ), ), ); await tester.pumpAndSettle(); - // Ожидаем, что будет отображено сообщение об ошибке await expectLater(find.byType(ErrorWidget), findsOneWidget); }); @@ -86,14 +90,15 @@ 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( - injection: mockDependency, + dependency: mockDependency, builder: (context) => Container(), ), ); @@ -113,15 +118,13 @@ void main() { final mockDependency1 = MockDependencyContainer(); final mockDependency2 = MockDependencyContainer(); - - when(mockDependency1.init).thenAnswer((_) async {}); - when(mockDependency2.init).thenAnswer((_) async {}); - + when(mockDependency1.inject).thenAnswer((_) async {}); + when(mockDependency2.inject).thenAnswer((_) async {}); // Строим первый виджет await tester.pumpWidget( DependencyScope( - injection: mockDependency1, + dependency: mockDependency1, builder: (context) => Container(), ), ); @@ -132,7 +135,7 @@ void main() { // Строим новый виджет с другой зависимостью await tester.pumpWidget( DependencyScope( - injection: mockDependency2, + dependency: mockDependency2, builder: (context) => Container(), ), ); @@ -140,7 +143,6 @@ void main() { await tester.pumpAndSettle(); // Проверяем, что инициализация новой зависимости произошла - }); }); } diff --git a/test/test.dart b/test/test.dart index 2697b1d..a152bc5 100644 --- a/test/test.dart +++ b/test/test.dart @@ -2,7 +2,6 @@ import 'dependency_container_test.dart' as dependency_container_test; import 'dependency_provider_test.dart' as dependency_provider_test; import 'dependency_scope_test.dart' as dependency_scope_test; - void main() { dependency_provider_test.main(); dependency_scope_test.main(); From 520cd3c072040a14a661f961be39d9c3afcbf795 Mon Sep 17 00:00:00 2001 From: Alexander Bangert Date: Wed, 4 Dec 2024 15:19:03 +0500 Subject: [PATCH 3/3] dev --- CHANGELOG.md | 3 +-- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9840549..05f7992 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,7 @@ -## 4.0.0-dev +## 4.0.0 * Big Refactoring - ## 3.0.0 * Rename classes, all test passed, change readme diff --git a/pubspec.yaml b/pubspec.yaml index 235e047..f4873c4 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-dev +version: 4.0.0 homepage: https://www.contributors.info/repository/depend