Flutter Dependency Injection: ioc_container V1
ioc_container is an IoC Container for Dart and Flutter. It started about five months ago as a quick way to replace dependencies for testing but evolved into a comprehensive Dependency Injection library for Dart and Flutter. Version 1.0.0 rounds off the major features and weighs in at 81 lines of code according to test coverage. You should try it in your project, and here's why.
ioc_container on pub.dev
ioc_container on GitHub
Dependency Injection can simplify your Flutter app. When your app grows in complexity, it becomes difficult to manage the construction and disposal of objects. You will find that some of your services depend on other services, and some services should exist for the lifetime of your app, while other services should only exist for a given widget's life. ioc_container allows you to configure factories so that services can access other services when needed and easily switch between Singleton (one per app) or Transient (fresh instance every time).
Dependency Injection is an established approach, and you can bring your knowledge of DI from other platforms to Flutter. The library takes inspiration from DI in .NET and other technologies like Java.
Saying all that, you can also use ioc_container as a service locator. Just declare the container in a global location, and you can access it anywhere. That means you can create scoped dependencies for a widget and dispose of them when you dispose of the widget.
Performance is important, and the benchmarks show that ioc_container easily holds up to the performance of libraries that do similar things. Check out the benchmarks here. Measurements are in microseconds and get operations to occur in fractions of a millisecond, so you can be sure it won't slow your app down.
Manage Async Initialization
ioc_container 1.0.0 brings an API for handling async initialization. Services don't always start up correctly, but you can still define your services as singletons and use the retry package to make multiple initialization attempts. The getAsynSafe() method ensures the container doesn't store the failed attempt. Check out the Flutter example.
ioc_container also simplifies adding, initializing, and testing Firebase in your app. Check out the documentation here.
Use Mocks or Fakes for Testing
ioc_container makes it easy to have a single composition root. This means you configure all your dependencies in one place instead of spreading that throughout the app. Replacing a given dependency with a fake or a mock is easy when it comes to testing. You can generate mocks with Mockito and then replace them like this.
Change Your App Without Changing Code
You will probably need to change parts of your app at some point. For example, you might load data from Firebase instead of a local database. If you use DI, you can write a new class and swap it in without changing the other code in your app. This is what we mean when we talk about the Liskov Substitution Principle, which is part of the SOLID principles.
Sometimes, you may need objects for the lifespan of a widget. In this case, call scoped() to get a scoped container. You can specify a dispose method for each dependency, and on widget dispose, you can call dispose on the container. This means you can clean up after creating dependencies, but this is not mandatory.
Flutter sometimes feels like an uncharted sea, but you can follow established approaches that have been useful on other platforms. DI makes it easy to manage your dependencies, and ioc_container makes DI simple. Even if you don't want to follow the traditional DI pattern, ioc_container is perfect as a simple factory management tool or a service locator. Most importantly, it is fast and simple, so you can easily understand how it works. Try it out and reach out on GitHub if you have issues.