ProxyProvider and MultiProvider

State management in Flutter often involves multiple providers working together. In many real-world applications, one provider may depend on another, or you may need to supply several providers to the widget tree at once.

This is where ProxyProvider and MultiProvider come in.

  • MultiProvider allows you to combine multiple providers in a clean and organized way.
  • ProxyProvider allows you to create dependencies between providers, ensuring one provider can consume data from another.

Both are part of the provider package, which is one of the most widely used state management solutions in Flutter.

In this post, we’ll explore these concepts in depth with detailed explanations, examples, and best practices.


Why Do We Need ProxyProvider and MultiProvider?

Without MultiProvider

If you want to provide multiple values using the provider package, you might write something like this:

return Provider<A>(
  create: (_) => A(),
  child: Provider<B>(
create: (_) =&gt; B(),
child: Provider&lt;C&gt;(
  create: (_) =&gt; C(),
  child: MyApp(),
),
), );

This works, but it becomes nested and hard to read as the number of providers grows.

With MultiProvider

Instead, you can use MultiProvider to flatten this structure:

return MultiProvider(
  providers: [
Provider&lt;A&gt;(create: (_) =&gt; A()),
Provider&lt;B&gt;(create: (_) =&gt; B()),
Provider&lt;C&gt;(create: (_) =&gt; C()),
], child: MyApp(), );

Cleaner, right?


Why ProxyProvider?

Sometimes one provider depends on another. For example:

  • You might have a User provider and a UserRepository provider.
  • UserRepository depends on the current user.
  • If the User changes, UserRepository should also update.

In such cases, ProxyProvider helps by injecting one provider into another dynamically.


MultiProvider in Detail

What is MultiProvider?

MultiProvider is a widget that takes a list of providers and supplies them all at once to the widget tree. It’s essentially a convenience wrapper around nested providers.

Syntax

MultiProvider(
  providers: [
Provider&lt;A&gt;(create: (_) =&gt; A()),
Provider&lt;B&gt;(create: (_) =&gt; B()),
], child: MyApp(), );

Example of MultiProvider

void main() {
  runApp(
MultiProvider(
  providers: &#91;
    ChangeNotifierProvider(create: (_) =&gt; Counter()),
    Provider(create: (_) =&gt; ApiService()),
  ],
  child: MyApp(),
),
); }

Here:

  • Counter is a ChangeNotifier used for managing counter state.
  • ApiService provides backend API calls.
  • Both are available anywhere inside MyApp.

When to Use MultiProvider?

  • When your app uses more than one provider.
  • When you want to avoid nesting providers manually.
  • To improve readability and maintainability.

ProxyProvider in Detail

What is ProxyProvider?

ProxyProvider allows one provider to depend on another provider. Whenever the dependency provider changes, the proxy provider automatically rebuilds with the new value.

It is useful when the value of a provider depends on data from another provider.

Syntax

ProxyProvider<A, B>(
  update: (context, a, b) => B(a),
)

Here:

  • A is the dependency provider.
  • B is the proxy provider that depends on A.
  • The update method creates or updates B whenever A changes.

Example: User and UserRepository

class User {
  final String id;
  User(this.id);
}

class UserRepository {
  final User user;
  UserRepository(this.user);

  String fetchData() {
return "Data for user: ${user.id}";
} } void main() { runApp(
MultiProvider(
  providers: &#91;
    Provider&lt;User&gt;(create: (_) =&gt; User("12345")),
    ProxyProvider&lt;User, UserRepository&gt;(
      update: (_, user, __) =&gt; UserRepository(user),
    ),
  ],
  child: MyApp(),
),
); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) {
final repository = Provider.of&lt;UserRepository&gt;(context);
return MaterialApp(
  home: Scaffold(
    body: Center(child: Text(repository.fetchData())),
  ),
);
} }

Explanation:

  • User is provided first.
  • UserRepository is created using ProxyProvider, depending on User.
  • If the User changes, UserRepository updates automatically.

Types of ProxyProvider

The provider package provides different variants of ProxyProvider depending on the number of dependencies.

1. ProxyProvider

Depends on one provider.

ProxyProvider<User, UserRepository>(
  update: (_, user, __) => UserRepository(user),
)

2. ProxyProvider2

Depends on two providers.

ProxyProvider2<User, ApiService, UserRepository>(
  update: (_, user, api, __) => UserRepository(user, api),
)

Here, UserRepository depends on both User and ApiService.


3. ProxyProvider3 and Beyond

Similarly, you can use ProxyProvider3, ProxyProvider4, and so on, up to ProxyProvider6.

Example with 3 dependencies:

ProxyProvider3<User, ApiService, Logger, UserRepository>(
  update: (_, user, api, logger, __) => UserRepository(user, api, logger),
)

Combining ProxyProvider and MultiProvider

In real apps, you often use ProxyProvider inside MultiProvider.

Example: Auth Example

void main() {
  runApp(
MultiProvider(
  providers: &#91;
    ChangeNotifierProvider(create: (_) =&gt; AuthService()),
    ProxyProvider&lt;AuthService, ApiService&gt;(
      update: (_, auth, __) =&gt; ApiService(auth.token),
    ),
  ],
  child: MyApp(),
),
); }

Explanation:

  • AuthService provides authentication details.
  • ApiService depends on AuthService for a token.
  • If the token changes, ApiService updates automatically.

Best Practices

  1. Keep Providers Small and Focused
    • Each provider should manage a single concern (e.g., authentication, API calls, cart state).
  2. Use MultiProvider for Clarity
    • Always wrap multiple providers in MultiProvider.
  3. Use ProxyProvider for Dependencies
    • Whenever one provider depends on another, use ProxyProvider.
  4. Avoid Unnecessary Dependencies
    • Do not chain too many providers together unnecessarily. It complicates state flow.
  5. Test Your Providers
    • Providers can be easily mocked and tested. Keep them independent of UI.

Common Mistakes

  1. Forgetting to Add ProxyProvider in MultiProvider
    • If ProxyProvider is not declared after its dependency provider, it will throw an error.
  2. Using BuildContext in Providers
    • Keep providers independent of UI context.
  3. Not Disposing Resources
    • If your provider uses controllers or streams, dispose them properly in dispose.
  4. Creating Large Provider Graphs
    • Break them into smaller providers for readability.

Real-World Scenarios for ProxyProvider and MultiProvider

  1. Authentication + API Service
    • Auth provider supplies tokens.
    • API service provider uses the token.
  2. User Preferences + Theme Provider
    • Preferences provider stores dark/light mode.
    • Theme provider uses it to apply app theme.
  3. Cart + Product Service
    • Cart provider stores selected items.
    • Product service provider fetches product details.
  4. Localization
    • Language provider supplies current language.
    • Localization service uses it to provide translated strings.

Advanced Example: Chained Dependencies

void main() {
  runApp(
MultiProvider(
  providers: &#91;
    Provider(create: (_) =&gt; Config()),
    ProxyProvider&lt;Config, ApiService&gt;(
      update: (_, config, __) =&gt; ApiService(config.baseUrl),
    ),
    ProxyProvider&lt;ApiService, UserRepository&gt;(
      update: (_, api, __) =&gt; UserRepository(api),
    ),
    ProxyProvider&lt;UserRepository, UserManager&gt;(
      update: (_, repo, __) =&gt; UserManager(repo),
    ),
  ],
  child: MyApp(),
),
); }

Here, we chain multiple providers:

  • ConfigApiServiceUserRepositoryUserManager
  • Each depends on the previous one.
  • Updates propagate automatically.

Performance Considerations

  • ProxyProvider only rebuilds when dependencies change. This keeps performance optimized.
  • Use Consumer widgets or Selector to minimize widget rebuilds.
  • Avoid putting too many dependencies in a single provider.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *