Skip to content

[RFC] Renaming the parameter 'builder' of providers #259

Description

@rrousselGit

Hello!

TD;DR:
This proposal is for a breaking change: renaming builder of Provider/ChangeNotifierProvider/..., to either initialValue or initialValueBuilder.

Which means before:

Provider<int>(
  builder: (context) => 42,
);

after:

Provider<int>(
  initialValue: (context) => 42,
);

or initialValue -> initialValueBuilder.

Feel free to suggest other names or express any point of view on the topic.

Motivation

Currently, the default constructor of providers all has a named required parameter builder, that builds a value once.

But this name causes a few issues.

  • it's misleading.
    In the context of Flutter, a parameter named builder is usually a function that returns a widget, not a model. Such as:

    StreamBuilder(
      builder: ...
    )

    Renaming it to will remove the confusion.
    And a name such as initialValue also makes it more apparent that builder will not be called again.

  • it prevents fusing *Provider and *ProxyProvider.
    These two widgets are very similar.
    Usually, *Provider is just syntax sugar for *ProxyProvider with only an initialBuilder like so:

    Provider(
      builder: (_) => 42,
    );

    is equivalent to:

    ProxyProvider<*, int>(
      initialBuilder: (_) => 42,
      builder: (_, __, value) => value,
    );

    But it is currently impossible to merge both these widgets because they have both a parameter named builder but with a different prototype.

    By renaming builder to something more expressive, it is possible to host both *Provider and *Proxy under the same class.

    We could have:

    ChangeNotifierProvider(
      initialValue: (_) => MyNotifier(),
    );

    Then we'd remove ProxyProvider.
    And add an extra value or valueBuilder parameter on Provider:

    ChangeNotifierProvider(
      initialValue: (_) => MyNotifier(),
      value: (context, notifier) {
        // Works like *ProxyProvider.builder
        // we can safely call `Provider.of` here
        return notifier
          ..foo = Provider.of<Foo>(context)
      },
    );

    Of course, we'd still have numeric syntax sugar like ProxyProvider2/..., but without the Proxy keyword:

    Provider1<Dependency, Result>(
      value: (context, Dependency dep, Result previous) {
        return Result(dep);
      },
    )
    
  • it prevents using builder for an actual "builder" that behaves likes Consumer/StreamBuilder.

    For example, a common use-case is to do:

    Provider<T>(
      builder: (_) => T(),
      child: Consumer<T>(
        builder: (_, value, __)  {
          // TODO: use `value`
        },
      ),
    )

    But that's pretty verbose.

    We could simplify it to:

    Provider<T>(
      initialValue: (_) => T(),
      builder: (_, value, __) {
        // TODO: use `value`
      },
    )

    Which would be strictly equivalent to the previous code – just smoother to write.
    But such simplification is not possible because builder is already taken.

Transition

This renaming could be done with a smooth transition using @deprecated.
In the v3.x, both builder and the new name could be on the widget.
builder would then be marked as @deprecated.

On the other hand, the other listed changes (fusing *Provider & *ProxyProvider or the Consumer shorthand) would not be part of the v3 but instead the v4 – which will also include loading.

Thoughts?

Feel free to 👍 or 👎 if you agree/disagree, or comment to make suggestions!

Metadata

Metadata

Assignees

No one assigned

    Labels

    breakingrequires a breaking changeenhancementNew feature or requesthelp wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions