Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update BlocBuilder and BlocListener to use a deps API for simpler state comparisons #4295

Open
ricardodalarme opened this issue Dec 5, 2024 · 5 comments
Assignees
Labels
question Further information is requested waiting for response Waiting for follow up

Comments

@ricardodalarme
Copy link

ricardodalarme commented Dec 5, 2024

Description

One thing that’s always been a bit frustrating when working with BlocBuilder and BlocListener is how verbose the buildWhen function can get. For example, if you need to rebuild the widget only when specific parts of the state change, you end up writing something like this:

BlocBuilder<ABloc, AState>(
  buildWhen: (previous, current) =>
      previous.currentPassword != current.currentPassword &&
      previous.newPassword != current.newPassword &&
      previous.confirmNewPassword != current.confirmNewPassword,
  builder: (context, state) => child,
)

While this works, it can quickly become tedious and hard to read, especially as the state grows more complex. It feels like there’s room for improvement to make this cleaner and more intuitive.

Desired Solution

It would be great if we could replace the buildWhen function with a deps parameter that takes a function to define the specific parts of the state we care about. The library would then handle comparing these properties under the hood. Here's what it could look like:

BlocBuilder<ABloc, AState>(
  deps: (state) => [state.currentPassword, state.newPassword, state.confirmNewPassword],
  builder: (context, state) => child,
)

Internally, the Bloc library could:

  1. Call deps with both the previous and current state.
  2. Compare the extracted properties.
  3. Trigger a rebuild only if the properties have changed.

This would make the code much more readable and concise while keeping the same level of flexibility.

On top of that, adding a lint rule to encourage proper use of deps would be a nice touch. For example, it could check that all the necessary fields are included in deps or flag unused fields in the state.

Potential Challenges

  • Making sure the internal comparison of deps handles mutable lists correctly to avoid issues with false positives or negatives.
  • Ensuring backward compatibility so existing buildWhen logic still works as expected.
@felangel
Copy link
Owner

felangel commented Dec 5, 2024

Hi @ricardodalarme 👋
Thanks for opening an issue!

I highly recommend using BlocSelector for all the cases you described. I'm actually planning to deprecate and remove buildWhen in an upcoming release.

Let me know if BlocSelector does not address your requirements and I'm happy to continue the conversation, thanks! 🙏

@felangel felangel added question Further information is requested waiting for response Waiting for follow up labels Dec 5, 2024
@felangel felangel self-assigned this Dec 5, 2024
@h3x4d3c1m4l
Copy link

h3x4d3c1m4l commented Dec 5, 2024

I don't think BlocSelector would be a solution for these cases, because often the properties of the state we need when building do not match with the properties we like to watch for changes. As such I don't think BlocSelector fills that gap.

The proposal would imo bring a better balance between flexibility, speed (in terms of unnecessary widget builds) and clean code.

It also looks a bit like React's useEffect, e.g. useEffect with dependencies.

@ricardodalarme
Copy link
Author

I completely agree with @h3x4d3c1m4l's.

Using BlocSelector instead of BlocBuilder introduces additional verbosity due to the need to declare the third generic type. One key goal of my proposal is to reduce this verbosity while keeping the API intuitive and developer-friendly.

I've been using Bloc in professional projects for over three years now, and I’ve rarely seen BlocSelector used in practice. In most cases, it tends to confuse developers because BlocSelector and BlocBuilder essentially aim to solve the same problem. The primary difference lies in their syntax, but BlocSelector makes it harder to work with multiple properties of the state object. While Dart’s new records provide a workaround, they’re still far too verbose for typical use cases.

Additionally, my proposal extends beyond just BlocBuilder; it also applies to BlocListener. Deprecating buildWhen in favor of BlocSelector risks creating inconsistencies across the API, as developers would have to adopt one approach for BlocBuilder and a different one for BlocListener. This lack of standardization could make the library less approachable, especially for newcomers.

Instead, I’d suggest considering the opposite approach: deprecate BlocSelector in favor of enhancing BlocBuilder with a deps feature as outlined in this issue. This would provide a unified API for both BlocBuilder and BlocListener, improving both flexibility and simplicity without adding additional concepts for developers to learn.

Let me know your thoughts! I’d love to keep the discussion going. 🙏

@felangel
Copy link
Owner

felangel commented Dec 5, 2024

I don't think BlocSelector would be a solution for these cases, because often the properties of the state we need when building do not match with the properties we like to watch for changes. As such I don't think BlocSelector fills that gap.

Can you please share some real-world code samples to help me better understand? I don't fully understand why you'd want to be watching/rebuilding in response to properties on a state that aren't directly rendered in the UI.

The proposal would imo bring a better balance between flexibility, speed (in terms of unnecessary widget builds) and clean code.

It also looks a bit like React's useState, e.g. useState with dependencies.

React's useEffect is actually quite problematic in my opinion. It gets misused all the time and can cause subtle/hard to debug issues. I would love to better understand what use-cases you have in mind that require a buildWhen and cannot be solved using a BlocSelector. It's also worth noting that buildWhen does not actually guarantee a build will not occur (it's purely just for micro-optimizations).

@felangel
Copy link
Owner

felangel commented Dec 5, 2024

I completely agree with @h3x4d3c1m4l's.

Using BlocSelector instead of BlocBuilder introduces additional verbosity due to the need to declare the third generic type. One key goal of my proposal is to reduce this verbosity while keeping the API intuitive and developer-friendly.

The generics should generally be able to be inferred so that you don't need to explicitly specify the three types. In addition, you can use context.select which is even less verbose than BlocBuilder.

I've been using Bloc in professional projects for over three years now, and I’ve rarely seen BlocSelector used in practice.

That's likely because BlocSelector was introduced more recently but in my experience context.select and BlocSelector are very useful and are used quite often in projects I've worked on.

In most cases, it tends to confuse developers because BlocSelector and BlocBuilder essentially aim to solve the same problem.

BlocSelector allows you to re-render UI when some part or parts of the state change whereas BlocBuilder allows you to re-render UI when any part of the state changes. They are similar but don't quite solve the same problem imo. Also, as I mentioned above, buildWhen does not actually guarantee builds will not occur (e.g. if the parent widget rebuilds, the BlocBuilder will also rebuild despite the value returned by buildWhen). BlocSelector is much better suited for cases where you want to optimize rebuilds of your widget in my opinion.

The primary difference lies in their syntax, but BlocSelector makes it harder to work with multiple properties of the state object. While Dart’s new records provide a workaround, they’re still far too verbose for typical use cases.

Can you please share some real-world code snippets that illustrate how you use buildWhen such that BlocSelector would be too verbose? I'm not sure I fully understand the problem here and I wonder whether your state couldn't be structured better to improve some of the cases you're describing.

Additionally, my proposal extends beyond just BlocBuilder; it also applies to BlocListener. Deprecating buildWhen in favor of BlocSelector risks creating inconsistencies across the API, as developers would have to adopt one approach for BlocBuilder and a different one for BlocListener. This lack of standardization could make the library less approachable, especially for newcomers.

Instead, I’d suggest considering the opposite approach: deprecate BlocSelector in favor of enhancing BlocBuilder with a deps feature as outlined in this issue. This would provide a unified API for both BlocBuilder and BlocListener, improving both flexibility and simplicity without adding additional concepts for developers to learn.

As I mentioned above, deps and it's usage in React's useEffect are far from ideal in my opinion. It's very easy to miss a dependency or have weird behavior due to equality comparison checks that makes debugging pretty painful. I actually tend to avoid useEffect as much as possible for those reasons. With BlocSelector the deps are inferred and it's not possible to accidentally forget a dependency.

Regarding the unified API, we'd also deprecate and remove listenWhen in favor of an equivalent to BlocSelector for consistency. I don't intend to make asymmetrical changes to the API so I wouldn't worry about that.

Let me know your thoughts! I’d love to keep the discussion going. 🙏

Looking forward to hear back and learning more about your specific use-cases, thanks! 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested waiting for response Waiting for follow up
Projects
None yet
Development

No branches or pull requests

3 participants