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.

  1. It's a simple widget that wrap a simple InheritedWidget
  2. You can inject one or multiple dependencies at once
  3. This widget will call your dependencies' dispose methods*
  4. It allows you to override children's injected dependencies
  5. 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!');
            },
        );
      }
    }

Libraries

flutter_inject
A simple dependency injection widget based on standard Flutter's InheritedWidget