flutter_inject 1.0.3 flutter_inject: ^1.0.3 copied to clipboard
A simple dependency injection widget based on standard Flutter's InheritedWidget
flutter_inject #
A simple dependency injection widget based on standard Flutter's InheritedWidget.
Keep it simple #
Flutter have been made with a built in dependency injection system: InheritedWidget.
- Injected dependencies are already scoped glad to the BuildContext.
- Memory is automatically cleaned when the InheritedWidget is removed from the Widget tree.
The Inject package respects Flutter's architecture.
- It's a simple widget that wrap a simple InheritedWidget
- You can inject one or multiple dependencies at once
- This widget will call your dependencies' dispose methods*
- It allows you to override children's injected dependencies
- You can also reuse parent's injected dependencies
1. Here's the simple InheritedWidget behind this package: #
class _Injected<T> extends InheritedWidget {
static T? get<T>(BuildContext context) {
final injected = context.dependOnInheritedWidgetOfExactType<_Injected<T>>();
return injected?.instance;
}
const _Injected(this.instance, {super.key, required super.child});
final T instance;
@override
bool updateShouldNotify(_Injected<T> oldWidget) => false;
}
2. Inject as many dependencies as you want ! #
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
// Inject multiple dependencies
return InjectAll(
dependencies: [
Dependency<MyApi>((context) => MyApiImpl()),
Dependency<MyRepository>((context) => MyRepositoryImpl(
// You can depend on any dependency previously injected
api: Dependency.get<MyApi>(context),
)),
],
builder: (context) {
// Then retrieve your dependencies anywhere in the widget tree
final repository = Dependency.get<MyRepository>(context);
return MyView();
},
);
}
}
class MyView extends StatelessWidget {
const MyView({super.key});
@override
Widget build(BuildContext context) {
// Inject one dependency
return Inject<MyLogic>(
factory: (context) => MyLogic(
// You can depend on any dependency previously injected
repository: Dependency.get<MyRepository>(context),
),
builder: (context) {
// Then retrieve you dependency anywhere in the widget tree
final logic = Dependency.get<MyLogic>(context);
return Scaffold(body: Center(child: Text(child: logic.getTitle())));
},
);
}
}
3. Never forget again to clean up you app memory ! #
// The Inject widget will call the .dispose() method of any injected dependencies !
class MyLogic {
final controller = StreamController<int>();
@override
void dispose() => controller.close();
}
4. Make tests more efficient by simply overriding children's dependencies #
// Here's an example of test with Mocktail (it works the same with Mockito)
class _MyRepositoryMock extends Mock implements MyRepository {}
void main() {
late _MyRepositoryMock mockedRepository;
setUp(() {
mockedRepository = _MyRepositoryMock();
});
testWidgets('Counter increments', (WidgetTester tester) async {
// Stub behaviour of MyRepository methods
when(() => mockedRepository.getToDos()).thenAnswer((_) => [Todo('Bring kids to school')]);
// Build your widget and trigger a frame.
await tester.pumpWidget(
MaterialApp(
// Inject a mocked version of MyRepository
home: Inject<MyRepository>(
// Override children's MyRepository injection
override: true,
factory: (context) => mockedRepository,
builder: (context) => const MyView(),
),
),
);
// Verify that no Todo list have been rendered
expect(find.text('Bring kids to school'), findsNothing);
// Tap the download button (to show the Todo list).
await tester.tap(find.byIcon(Icons.download));
await tester.pump();
expect(find.text('Bring kids to school'), findsOneWidget);
});
});
}
5. Reuse existing parents' injected instances #
class MyView extends StatelessWidget {
const MyView({super.key});
@override
Widget build(BuildContext context) {
return Inject<MyRepository>(
// If a MyRepository instance has already been injected in the tree then we'll reuse it
// Else no worries, your widget will inject its own instance
useExistingInstance: true,
factory: (context) => MyRepositoryImpl(
api: Dependency.get<MyApi>(context),
),
builder: (context) {
final repository = Dependency.get<MyRepository>(context);
return Text('Hello World!');
},
);
}
}