Skip to content

Conversation

@albertodev01
Copy link
Contributor

I have created the new ChangeNotifierBuilder widget, taking care of documenting it very well and also updating the existing documentation. May this be ok?

This PR is for the #95967 issue so please read the proposal made in the Issue if you are looking for additional context.

There are no breaking changes!

Pre-launch Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement].
  • I signed the [CLA].
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is [test-exempt].
  • All existing and new tests are passing.

@flutter-dashboard flutter-dashboard bot added the framework flutter/packages/flutter repository. See also f: labels. label Jan 26, 2022
@kaboc
Copy link
Contributor

kaboc commented Jan 26, 2022

I made a PR (#91179) last year to add something similar to listenCondition to ValueListenableBuilder, but it was declined because the reviewer found it confusing that the condition was ignored at the first build. Does the listenCondition in this widget have no issue like that?

@mateusfccp
Copy link
Contributor

I made a PR (#91179) last year to add something similar to listenCondition to ValueListenableBuilder, but it was declined because the reviewer found it confusing that the condition was ignored at the first build. Does the listenCondition in this widget have no issue like that?

Well, it does make sense. I would rather call it rebuildCondition or something alike. But maybe we should simply not include this API in the widget. Although it may be useful, it's more specific and may indeed cause confusion. But IDK

@kaboc
Copy link
Contributor

kaboc commented Jan 26, 2022

Here is an example of what can happen.

listen_condition

Source code
class CounterNotifier extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}
class Counter1 extends StatelessWidget {
  const Counter1();

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierBuilder(
      changeNotifier: notifier,
      listenCondition: () => notifier.count.isEven,
      builder: (_, __) => Text('Even: ${notifier.count}'),
    );
  }
}

class Counter2 extends StatelessWidget {
  const Counter2();

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierBuilder(
      changeNotifier: notifier,
      listenCondition: () => notifier.count.isOdd,
      builder: (_, __) => Text('Odd: ${notifier.count}'),
    );
  }
}
final notifier = CounterNotifier();

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App();

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage();

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  bool _visible = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Counter1(),
            if (_visible) ...[
              const SizedBox(height: 16.0),
              const Counter2(),
            ],
            const SizedBox(height: 16.0),
            ElevatedButton(
              child: Text('${_visible ? 'Hide' : 'Show'} odd'),
              onPressed: () {
                setState(() => _visible = !_visible);
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: notifier.increment,
      ),
    );
  }
}

@albertodev01
Copy link
Contributor Author

albertodev01 commented Jan 26, 2022

If you think that listenCondition may be confusing, we could remove it!


@kaboc The behaviour you're experiencing happens because you're using the same ChangeNotifier for two different things (showing even and odd numbers) so the logic may be flawed. But I agree that this may be confusing, I might remove it 🙂

@mateusfccp
Copy link
Contributor

If you think that listenCondition may be confusing, we could remove it!

Well, I am not strongly opinionated about it, and also am not from the Flutter team, so I don't know what they will think about it, haha

@kaboc
Copy link
Contributor

kaboc commented Jan 26, 2022

I actually want the feature in both ChangeNotifierBuilder and ValueListenableBuilder, and so do some other developers, I guess, but it may be better to remove it unless there's an alternative way without the issue. Sorry for cutting in, btw.

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a more limited AnimatedBuilder that instead of taking a general Listenable is limited to ChangeNotifiers. Why that limitation? What will this fix? What use cases does it enable?

@Hixie
Copy link
Contributor

Hixie commented Jan 27, 2022

Other than the name, I don't really understand the value of this over AnimatedBuilder.

We could do typedef ListenableBuilder = AnimatedBuilder or typedef ChangeNotifierBuilder = AnimatedBuilder if the name issue makes it worth considering this change.

@mateusfccp
Copy link
Contributor

I think the main hassle is indeed the confusion that the name AnimatedBuilder causes, specially to newcomers. I've seen people using Provider simply because they didn't know that they could rebuild ChangeNotifier with AnimatedBuilder

@mateusfccp
Copy link
Contributor

We could do typedef ListenableBuilder = AnimatedBuilder or typedef ChangeNotifierBuilder = AnimatedBuilder if the name issue makes it worth considering this change.

I suggested it recently in #19255. I think the idea of the author of this PR is that by providing a new widget we don't have any potentially breaking change.

@albertodev01
Copy link
Contributor Author

albertodev01 commented Jan 27, 2022

Thank you everyone for your reviews! 🙂 I'm wrapping up a few answers here:


@goderbauer

This seems to be a more limited AnimatedBuilder that instead of taking a general Listenable is limited to ChangeNotifiers. Why that limitation?

I think that AnimatedBuilder is too much "generic" because a Listenable can be a lot of things: any kind of animation, a ValueListenable (and then a ValueNotifier too) and a ChangeNotifier. I see this as a problem because it's too broad and it can be used on different things:

  • handling animations
  • handling the state (with a ChangeNotifier or a ValueNotifier for example)

I see it as a sort of "generic listener" whose name may also be confusing. If you added more concrete Listenable classes in the future for another different purpose, it would still be used in AnimatedBuilder. This is why I fear it's taking a too much "broad" or "generic" approach. At this point, I think we may ask the same questions for value listenables:

  • Why ValueListenableBuilder exists?
  • A ValueNotifier is a Listenable so why creating a ValueListenableBuilder when we could slap everything inside an AnimatedBuilder?

What makes this class "specialized" that it only works on ChangeNotifiers? Why wouldn't it take a general Listenable?

I wouldn't take a Listenable to not make any confusion. For example ValueListenableBuilder could surely ask for a Listenable rather than a ValueListenable but then it wouldn't be specialized for THAT type anymore 🙂

In my view, I think that we should give AnimatedBuilder less responsibilities because at the moment (as I've said in the previous answer) it's too generic and works for too many things. I see this as a source of confusion.

From my small view, I saw some people not knowing that a ChangeNotifier could be listened in an AnimatedBuilder, mostly because the name made them think of a different purpose. Other than that, I recognize that Flutter has builders for "things that change" such as

  • A ValueListenable type has a ValueListenableBuilder
  • A Stream type has a StreamBuilder
  • A Future type has a FutureBuilder
  • A PageRoute type has a PageTransitionsBuilder
  • ... and so on!

I would really like to see together:

  • ValueListenableBuilder for value notifiers
  • ChangeNotifierBuilder for change notifiers
  • AnimatedBuilder for generic listenables

I don't think it's a matter of this being "the same as AnimatedBuilder" because this reasoning would also apply to ValueListenableBuilder, which in the end simply listens to a Listenable (the same thing AnimatedBuilder does). I think it's a matter of giving less "responsibilities" to a single widget (AnimatedBuilder) because I don't like the idea that too many things can fit in here.

Naming aside, which I think most of us agree should be changed, I would also update the documentation to sort of redirect or encourage developers to prefer using ValueListenableBuilder for value listenables and ChangeNotifierBuilder for change notifiers.

This is kind of odd. What's the use case for this?

You are referring to the listenCondition. The main use case I see is having a more granular control of the rebuilds. Various methods of a ChangeNotifier can call notifyListeners() and so, for each call, the builder is triggered. There might be a case where we want to listen only when a particular property of a notifier changes.

For example, a change notifier for a login action might be "stopped" being listened if we detected that there is no active internet connection. In this case, the builder wouldn't be called!


@Hixie

We could do typedef ListenableBuilder = AnimatedBuilder or typedef ChangeNotifierBuilder = AnimatedBuilder if the name issue makes it worth considering this change

Following the alias idea, then we could also make

  • typedef ValueListenableBuilder = AnimatedBuilder
  • typedef ChangeNotifierBuilder = AnimatedBuilder

and remove the widgets we currently have, but I don't really like this idea. I'd prefer keeping these 2 classes that specifically deal with a type. This is for the "builder" pattern that Flutter has and also classes may accept other members or functions in the future, being open to additions by nature.

I think that the same doubts we have about the usefulness of ChangeNotifierBuilder are the same that might arise if we think about ValueNotifierBuilder. In the end, both listen to a Listenable!

However, for the reasons I've expressed before, I think they're good to exist to not give AnimatedBuilder too much weigh. Personally, I don't really like the idea of using AnimatedBuilder for animations, change notifiers, value listenables and, who knows, maybe other kind of listenable classes in the future 🙂


What do you think of my point of view?

@Hixie
Copy link
Contributor

Hixie commented Jan 27, 2022

The difference between AnimatedBuilder and ValueListenableBuilder is that the callback for ValueListenableBuilder includes the value of the ValueListenable. ChangeListener, or rather, Listenable (ChangeNotifier is just an implementation of Listenable, they are in every other way equivalent) doesn't have a value, it only sends a signal.

AnimatedBuilder should really have been called ListenableBuilder, I agree with that. But other than that, I don't see what ChangeNotifier does that AnimatedBuilder doesn't.

@goderbauer
Copy link
Member

Yes! I agree that the name AnimatedBuilder is somewhat confusing and the fact that the docs for ChangeNotifier don't link to it is an oversight we need to fix. I could maybe get behind a typedef that exposes AnimatedBuilder under the name ListenableBuilder or something like that, but I don't see the value in duplicating the implementation of AnimatedBuilder under a different name and then maintaining that as a separate copy.

@albertodev01
Copy link
Contributor Author

@goderbauer and @Hixie since I understand that you lean towards the renaming idea, which of these approaches you think might be better?

  1. Changing the name from AnimatedBuilder to ListenableBuilder and then creating typedef AnimatedBuilder = ListenableBuilder;. Absolutely update the documentation for ChangeNotifier.
  2. Creating typedef ListenableBuilder = AnimatedBuilder;. Absolutely update the documentation for ChangeNotifier.

Personally, I'd prefer option (1) so renaming the widget to ListenableBuilder and adding a typedef to not break everything

@Hixie
Copy link
Contributor

Hixie commented Jan 27, 2022

I think given the amount of documentation (e.g. YouTube videos) that exists around the current name, it's probably best to do something like #2.

In either case, adding documentation in all these places is definitely a good thing.

@escamoteur
Copy link
Contributor

I find the idea with the typedef really good.

On the original idea of a listenCondition I would point to my functional_listener package that gives you rx like functions on ValueListeanables

@albertodev01
Copy link
Contributor Author

Closing this and creating a new one to improve the docs and add a typedef. Thanks everyone for the feedbacks!

@esDotDev
Copy link

esDotDev commented Nov 10, 2022

Curious where this landed, the typedef doesn't seem to exist?

I think updating the documentation missed the key issue: semantically it just reads wrong. Even though AnimatedBuilder can be used with any Listenable, it really shouldn't, because that's poor / incorrect semantics.

When we have something like AnimatedBuilder it causes the reader to think there must be an animation in play, either Animation or AnimationController is the intuitive assumption. If we care about writing good declarative code, seems like we want a proper ListenableBuilder type so the readers of our code are not misled/confused.

Apologies if it's already landed and I just don't see it...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants