Skip to content

Adding a new builder widget for ChangeNotifier #95967

@albertodev01

Description

@albertodev01

Use case

In the Flutter ecosystem there is a well-established way of listening to something that changes, for example:

  • A ValueListenable type has a ValueListenableBuilder
  • An AnimationController type has a AnimatedBuilder
  • A PageRoute type has a PageTransitionsBuilder
  • and so on...

So if you follow this pattern, a type that needs to be listened for changes has a FooBuilder widget associated. I would like to create a ChangeNotifierBuilder for consistency with the "builder" pattern we have.

Right now, we can use an AnimatedBuilder to listen for changes on a class mixed or extended with ChangeNotifier but this is not intuitive. Users might not know that both ChangeNotifier and Animation<T> extend Listenable (and so a ChangeNotifier can be used in an AnimatedBuilder).

I think that having a ChangeNotifierBuilder (a simple wrapper or subtype of AnimatedBuilder) would make more consistency in terms of naming builders. No performance benefits at all.

When you create a new Flutter app with flutter create --template=skeleton myapp there is a model class called SettingsController which uses a ChangeNotifier. It is listened in this way:

AnimatedBuilder(
  animation: settingsController,
  builder: (BuildContext context, Widget? child) { ... }
),

Since the builder is named " Animated Builder" I wouldn't expect something that is not an animation (settingsController) to be attached (even if it perfectly works). The ChangeNotifier doc doesn't even mention that it can be listened into an AnimatedBuilder (this should be added too to clarify!). I would have preferred something like this:

ChangeNotifierBuilder(
  changeNotifier: settingsController,
  builder: (BuildContext context, Widget? child) { ... }
),

Proposal

Right now we have the AnimatedWidget which listens on a generic Listenable widget.

The AnimatedBuilder is simply a subtype that adds a builder function:

class AnimatedBuilder extends AnimatedWidget {
  const AnimatedBuilder({
    Key? key,
    required Listenable animation,
    required this.builder,
    this.child,
  }) : assert(animation != null),
       assert(builder != null),
       super(key: key, listenable: animation);

  final TransitionBuilder builder;

  final Widget? child;

  @override
  Widget build(BuildContext context) {
    return builder(context, child);
  }
}

I would like to create a very similar widget for a ChangeNotifier as well:

class ChangeNotifierBuilder<T extends ChangeNotifier> extends AnimatedBuilder {
  const ChangeNotifierBuilder({
    Key? key,
    required T changeNotifier,
    required this.builder,
    this.child,
  }) : assert(animation != null),
       assert(builder != null),
       super(
         key: key, 
         animation: changeNotifier,
         builder: builder,
         child: child,
       );
}

This would mean that people should MyClass extends ChangeNotifier instead of MyClass with ChangeNotifier to use this widget. The class would have the 'builder' word in the name, which would be consistent with the other "listening" builder widgets.

For example, many people that use Flutter's built-in state management would also do something like this:

class Counter extends ChangeNotifier { ... }

class CounterState extends InheritedWidget {
  const CounterState ({
    Key? key,
    required this.counter,
    required Widget child,
  }) : super(key: key, child: child);

  final Counter counter;

  static CounterState of(BuildContext context) { ... }

  bool updateShouldNotify(CounterState old) { ... };
}

// And then in a widget
ChangeNotifierBuilder(
  changeNotifier: CounterState.of(context).counter,
  builder: (context, child) { ... }
);

May this be a good idea?

Metadata

Metadata

Assignees

No one assigned

    Labels

    c: proposalA detailed proposal for a change to Flutterd: api docsIssues with https://api.flutter.dev/frameworkflutter/packages/flutter repository. See also f: labels.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions