Last updated:
0 purchases
flutter ioc container
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 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 and anywhere in the widget tree.
This accesses the CounterController to increment and grab the current value
FloatingActionButton.extended(
icon: const Icon(Icons.add),
//Increment the value
onPressed: context<CounterController>().increment,
label: Text(
//Display the value
context<CounterController>().value.toString(),
style: Theme.of(context).textTheme.headlineMedium,
),
),
copied to clipboard
See the 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:
flutter_ioc_container: <latest version>
Run flutter pub get to download the dependencies.
Or, you can install the package from the command line:
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.
Use the builder in the compose function to add singleton or transient dependencies to the container.
Access the dependencies throughout the widget tree via the BuildContext
import 'package:flutter/material.dart';
import 'package:flutter_ioc_container/flutter_ioc_container.dart';
void main() {
runApp(
CompositionRoot(
compose: (builder) => builder.addSingleton((container) => 'test'),
child: MaterialApp(
home: Scaffold(
body: Builder(builder: (context) => const BasicWidget()),
),
),
),
);
}
class BasicWidget extends StatelessWidget {
const BasicWidget({super.key});
@override
Widget build(BuildContext context) => Text(context<String>());
}
copied to clipboard
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.
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(
compose: (builder) => builder.addServiceDefinition<DisposableResources>(
ServiceDefinition(
(container) => DisposableResources(),
dispose: (service) => service.dispose(),
),
),
child: MaterialApp(
home: Scaffold(
body: Builder(builder: (context) => const BasicWidget()),
),
),
),
);
}
class BasicWidget extends StatefulWidget {
const BasicWidget({super.key});
@override
State<BasicWidget> createState() => _BasicWidgetState();
}
class _BasicWidgetState extends State<BasicWidget> {
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<DisposableResources>().display);
}
copied to clipboard
See more on scoping here.
Async Injection #
If your dependency requires async initialization, you can do this using 'addAsync'. You can use the FutureBuilder widget to render the object when it is available. Here's an example:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_ioc_container/flutter_ioc_container.dart';
void main() {
runApp(
CompositionRoot(
compose: (builder) => builder.addSingletonAsync(
(container) async => Future<String>.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<BasicAsyncWidget> createState() => _BasicAsyncWidgetState();
}
class _BasicAsyncWidgetState extends State<BasicAsyncWidget> {
late final Future<String> future;
@override
void didChangeDependencies() {
// ignore: discarded_futures
future = context.getAsync<String>();
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(),
);
}
copied to clipboard
See more on async injection here.
Replace Dependencies with Test Doubles for Testing #
Pass a configure configureOverrides function into your root widget. This allows you to replace dependencies with test doubles for testing. See the example widget tests for a full example.
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 void Function(IocContainerBuilder builder)? configureOverrides;
@override
Widget build(BuildContext context) => CompositionRoot(
configureOverrides: configureOverrides,
compose: (builder) => builder
//Adds a singleton CounterController to the container
..addSingleton(
(container) => CounterController(),
),
// [...] See the example folder of this package for a full example
);
}
copied to clipboard
This example overrides the dependency with a MockValueNotifier
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<CounterController>((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);
});
copied to clipboard
See more on testing here.
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.