https://github.com/melbournedeveloper/flutter_ioc_container
A lightweight, flexible, and high-performance dependency injection and service location library for Flutter
https://github.com/melbournedeveloper/flutter_ioc_container
dart flutter mobiledevelopment
Last synced: about 1 year ago
JSON representation
A lightweight, flexible, and high-performance dependency injection and service location library for Flutter
- Host: GitHub
- URL: https://github.com/melbournedeveloper/flutter_ioc_container
- Owner: MelbourneDeveloper
- License: bsd-3-clause
- Created: 2022-10-24T23:50:41.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2024-06-30T23:51:37.000Z (over 1 year ago)
- Last Synced: 2025-03-20T15:58:25.079Z (about 1 year ago)
- Topics: dart, flutter, mobiledevelopment
- Language: C++
- Homepage: https://pub.dev/packages/flutter_ioc_container
- Size: 377 KB
- Stars: 14
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# flutter_ioc_container

Manage your dependencies in the widget tree, access them from the `BuildContext`, and replace them with test doubles for testing.
[ioc_container](https://pub.dev/packages/ioc_container) is a dependency injection and service location library for Dart. You can use it in Flutter as a service locator like the `GetIt` package. `flutter_ioc_container` is an extension for `ioc_container` that exposes the library throughout the widget tree so you can use it like `Provider`. It provides extension methods on `BuildContext` to allow you to get instances of your dependencies anywhere in the widget tree.
This accesses the `CounterController` to increment and grab the current value:
```dart
FloatingActionButton.extended(
icon: const Icon(Icons.add),
// Increment the value
onPressed: context().increment,
label: Text(
// Display the value
context().value.toString(),
style: Theme.of(context).textTheme.headlineMedium,
),
),
```
_See the [ioc_container](https://pub.dev/packages/ioc_container) documentation for a more comprehensive guide._
## Getting Started
### Installing the Package
Add the following line to your `pubspec.yaml` file under the dependencies section:
```yaml
dependencies:
flutter_ioc_container:
```
Run `flutter pub get` to download the dependencies.
Or, you can install the package from the command line:
```sh
flutter pub add flutter_ioc_container
```
### Basic Usage
- Put a `CompositionRoot` widget at the base of your widget tree. This propagates the container throughout the widget tree as an inherited widget.
- Specify the `configureBuild` parameter to configure the container.
- Access the dependencies throughout the widget tree via the `BuildContext`.
```dart
import 'package:flutter/material.dart';
import 'package:flutter_ioc_container/flutter_ioc_container.dart';
void main() {
runApp(
CompositionRoot.configureBuild(
MaterialApp(
home: Scaffold(
body: Builder(builder: (context) => const BasicWidget()),
),
),
(builder) => builder.addSingleton((container) => 'test'),
),
);
}
class BasicWidget extends StatelessWidget {
const BasicWidget({super.key});
@override
Widget build(BuildContext context) => Text(context());
}
```
### Scoping
If you need a set of dependencies that have a short life and you need to dispose of them afterward, something in the widget tree needs to hold onto a scoped container. Get a scoped container by calling `context.scoped()`. One approach is to put the scoped container in the `State` of a `StatefulWidget` and dispose of the contents in the `dispose()` method of the `State`.
This example creates a scoped container on `didChangeDependencies`. It exists for the lifespan of the state, and the resources get disposed when the widget tree disposes of this widget.
```dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_ioc_container/flutter_ioc_container.dart';
import 'package:ioc_container/ioc_container.dart';
class DisposableResources {
String display = 'hello world';
void dispose() {
// ignore: avoid_print
print('Disposed');
}
}
void main() {
runApp(
CompositionRoot.configureBuild(
MaterialApp(
home: Scaffold(
body: Builder(builder: (context) => const BasicWidget()),
),
),
(builder) => builder.add(
(container) => DisposableResources(),
dispose: (service) => service.dispose(),
),
),
);
}
class BasicWidget extends StatefulWidget {
const BasicWidget({super.key});
@override
State createState() => _BasicWidgetState();
}
class _BasicWidgetState extends State {
late final IocContainer scopedContainer;
@override
void didChangeDependencies() {
super.didChangeDependencies();
scopedContainer = context.scoped();
}
@override
void dispose() {
super.dispose();
unawaited(scopedContainer.dispose());
}
@override
Widget build(BuildContext context) =>
Text(scopedContainer().display);
}
```
See more on scoping [here](https://pub.dev/packages/ioc_container#scoping-and-disposal).
### Async Injection
If your dependency requires async initialization, you can do this using `addAsync`. You can use the [`FutureBuilder`](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html) widget to render the object when it is available. Async singletons come with a [guarantee of no reentrancy](https://pub.dev/packages/ioc_container/versions/2.0.1-beta#v2-and-async-locking)(async locking), so it is safe to grab the singleton anywhere in the app and the future will resolve once.
Here's an example:
```dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_ioc_container/flutter_ioc_container.dart';
void main() {
runApp(
CompositionRoot(
compose: BuildCompose(
(builder) => builder.addSingletonAsync(
(container) async => Future.delayed(
const Duration(seconds: 5),
() => 'Hello world!',
),
),
),
child: MaterialApp(
home: Scaffold(
body: Builder(builder: (context) => const BasicAsyncWidget()),
),
),
),
);
}
class BasicAsyncWidget extends StatefulWidget {
const BasicAsyncWidget({super.key});
@override
State createState() => _BasicAsyncWidgetState();
}
class _BasicAsyncWidgetState extends State {
late final Future future;
@override
void didChangeDependencies() {
// ignore: discarded_futures
future = context.getAsync();
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) => FutureBuilder(
// ignore: discarded_futures
future: future,
builder: (ctx, ss) => ss.connectionState == ConnectionState.done
? Text(ss.data!)
: const CircularProgressIndicator(),
);
}
```
See more on async injection [here](https://pub.dev/packages/ioc_container#async-initialization).
## Replace Dependencies with Test Doubles for Testing
Pass a `configureOverrides` function into your root widget. This allows you to replace dependencies with test doubles for testing. See the example widget [tests](example/test/widget_test.dart) for a full example.
```dart
class MyApp extends StatelessWidget {
const MyApp({
super.key,
this.configureOverrides,
});
// This allows us to override the dependencies for testing. Take a look at
// the widget tests
final ConfigureBuild? configureOverrides;
@override
Widget build(BuildContext context) => CompositionRoot(
compose: BuildCompose(
configureOverrides: configureOverrides,
(builder) => builder
// Adds a singleton CounterController to the container
..addSingleton(
(container) => CounterController(),
),
// [...] See the example folder of this package for a full example
),
);
}
```
This example overrides the dependency with a `MockValueNotifier`:
```dart
testWidgets('Basic Smoke Test', (tester) async {
final mockValueNotifier = MockValueNotifier();
await tester.pumpWidget(
MyApp(
// This is how you substitute dependencies with test doubles
configureOverrides: (builder) => builder
.addSingleton((container) => mockValueNotifier),
),
);
// Initial value
expect(find.text('0'), findsOneWidget);
// Tap the button
await tester.tap(find.byIcon(Icons.add));
await tester.pumpAndSettle();
// Verify value
expect(find.text('1'), findsOneWidget);
expect(find.text('0'), findsNothing);
// Ensure we're using the mock dependency
expect(mockValueNotifier.hasCalls, isTrue);
});
```
See more on testing [here](https://pub.dev/packages/ioc_container#testing).