Disco
Disco is a library introducing a new concept of providers that operate differently from those already present in the Flutter ecosystem. It was developed to overcome the challenges and limitations in the context of dependency injection.
Simple usage example
The package supports many features, like providers that accept arguments. But to keep things simple, here is a basic example to get you started:
-
Define a provider at the top level.
final modelProvider = Provider((context) => Model()); -
Insert a
ProviderScope
at the desired point in the widget tree to define the scope of the provider and make it accessible to the corresponding subtree.ProviderScope(providers: [modelProvider],child: MyWidget(),) -
Inject the provider directly inside a new stateless widget or a stateful widget’s state.
class InjectingWidget extends StatelessWidget {const InjectingWidget({super.key});@overrideWidget build(BuildContext context) {final model = modelProvider.of(context);return Text(model.toString());}}
What makes this library unique
Disco draws inspiration from both Provider and Riverpod — the two most widely used DI libraries in Flutter, both built around the concept of providers — while aiming to overcome their respective limitations. These libraries have significantly shaped how developers manage state and dependencies: Provider supports a simple, widget-tree–aligned scoping model, but does not allow multiple providers of the same type. Riverpod, by contrast, supports multiple providers of the same type, but relies on a globally structured architecture that breaks away from the widget tree.
Disco builds on the strengths of both while taking a different path. It is — to our knowledge — the first and only solution that supports multiple providers of the same type (without wrapper types or string keys) using a model that remains fully local and naturally integrated into the Flutter widget tree — while reducing the downsides of both to a minimum.
If you would like to explore in detail the pain points of Provider and Riverpod, and how Disco compares, see our page Comparison with alternatives.
Trade-offs
Pros
The pros of Disco are:
- The providers are scoped.
- The widget tree is fully leveraged.
- This keeps the architecture simple.
- The widget tree is fully leveraged.
- No global state is possible.
- Circular dependencies are impossible.
- Multiple providers of the same type are possible.
- There is no need to create wrapper types or rely on IDs such as strings.
- The API is very simple and feels natural to Flutter.
- Providers are equipped with
of(context)
andmaybeOf(context)
methods. - All you need is
BuildContext
. There is no additional class needed to inject the providers.
- Providers are equipped with
- The removal of a provider has an impact on its providing and each of its injections.
- Each of them is immediately characterized by a static error.
- The values held by the providers are immutable.
- While immutable, some instances allow for inner mutation.
- This is great: observables and signals can be passed down.
- While immutable, some instances allow for inner mutation.
- No reactivity is included.
- This library focuses on DI, so that state management solutions can focus on the reactivity.
- To include reactivity, provide a built-in or third-party observable/signal to the provider (e.g.
Signal
,ChangeNotifier
,Cubit
, …).
Cons
The cons of this library are:
- Providers might need to be lifted up or down in the widget tree, as requirements change.
- Modals spawn new widget trees, causing disconnection with the providers in the main tree.
- A special widget must be used to restore access to the providers in the main widget tree.
- It is not fully compile-time safe.
- The injection of a provider that cannot be found in any scope results in a runtime error.
In Disco’s defense regarding the last point:
- Total compile-time safety is not possible with an approach leveraging scoped DI, which is a pattern ubiquitously used in Flutter and third-party libraries (think about how many times you have already read
MediaQuery.of(context)
,GoRouter.of(context)
, …). - Disco providers also have a
maybeOf(context)
method, which can help if the presence of a provider cannot be guaranteed. - The throwable includes precise information in its stack trace to deduce the missing provider: filepath, line and column.
Keep in mind
As the authors of Disco, we believe this to be the most effective strategy for DI in Flutter. However, every solution has trade-offs. You can limit the impact of these trade-offs by running tests, doing code reviews, and following other crucial practices.