Skip to content

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:

  1. Define a provider at the top level.

    final modelProvider = Provider((context) => Model());
  2. 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(),
    )
  3. Inject the provider directly inside a new stateless widget or a stateful widget’s state.

    class InjectingWidget extends StatelessWidget {
    const InjectingWidget({super.key});
    @override
    Widget 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.
  • 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) and maybeOf(context) methods.
    • All you need is BuildContext. There is no additional class needed to inject the providers.
  • 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.
  • 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.