felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.82k stars 3.39k forks source link

weird BlocBuilder/buildWhen behaviour: wrong state when rebuilding parent widget. #3234

Closed maheini closed 2 years ago

maheini commented 2 years ago

Description

It's kind of a complex issue, but I'll try my best. Please have a look at my Widget tree first:

-MaterialApp
   ⤷StatefulWidget "Test"
        ⤷StatefulWidget "MyApp"  ->Containing my Cubit and a List as State variables
           -List
              ⤷BlocBuilder, wrapping each ListTile.

The Blocbuilder is designed to build only selected/unselected ListTiles, using 'buildWhen'. Now the problem:

  1. Let's assume I change the State of my Cubit.
  2. One of the ListTiles (e.g. "Tile1") got rebuild.
  3. The Widget "MyApp" gets rebuild (reason doesn't matter)
  4. Now the issue is visible: The builder Parameter inside BlocBuilder provides still the initial state on all ListTiles. Only "Tile1" has the state matching the one inside the Cubit object.

    How to reproduce / test

    Because of the big amount of boilerplate needed, I generated a repo with a minimal version to reproduce the error. To reproduce do as follow:

  5. Clone repo: Repo
  6. Run the app. You should see 4x this output (debugging-console): builder_state: -1 --- bloc_state: -1 (meaning all good)
  7. press "change cubit" button on the appbar. You can see output: builder_state: 0 --- bloc_state: 0 (meaning: 1 ListTile rebuild, state from builder == state of cubit)
  8. press "rebuild widget" button on top -> parent widget ("Test") calls setState() and forces the List to rebuild
  9. Now you get the final output: 1x builder_state: 0 --- bloc_state: 0 (1 ListTile was build with builder_state == cubit_state) and 3x builder_state: -1 --- bloc_state: 0 -> meaning: All previously unchanged ListTiles still receive the old state, even thought the cubit state has changed.

    Expected Behaviour

    Inside BlocBuilder->builder, the state should be exactly the one which cubit contains.

    Actual Behavior

    After Part-wise rebuild with BlocBuilder->buildWhen, the state isn't accurate anymore inside BlocBuider->builder.

    Environment

    flutter_bloc: 8.0.1 Flutter: 2.10.1 Windows 11, VSCode

    Other notes

    If I remove rebuildWhen, the error is gone (as expected, because all Items are now rebuild). The Problem also don't appear, when I use cubit.state instead of the given State inside BlocBuilder->builder.

    Additional

    Flutter Bloc is awesome, I really like this library. Thanks for all your effort, I hope I'm able to contribute with this issue.

Gene-Dana commented 2 years ago

Hi there ✌🏼 I looked briefly at the repo, and I'd like to circle back to this and look it over again, although recently I saw a few issues with ListTiles and on a whim, may you to try adding a key

ListTile(
   key: UniqueKey(),
...

Does that help at all?

maheini commented 2 years ago

Hi @Gene-Dana. Thanks for looking after it. I just tried with key: UniqueKey() inside ListTile, but no change in behaviour. I still get output: 4x builder_state: -1 --- bloc_state: -1 ->4 Tiles build, Bloc.state == builder state (changing state, wich triggers a rebuild of one listtile) 1x builder_state: 0 --- bloc_state: 0 ->1 Tile build, Bloc.state == builder state (rebuilding parent Widget, wich forces current item to rebuild ->all ListTiles do rebuild) 1x builder_state: 0 --- bloc_state: 0 ->1 Tile build with Bloc.state == builder state 3x builder_state: -1 --- bloc_state: 0 ->3 Tile build with Bloc.state != builder state

Gene-Dana commented 2 years ago

Okay let me look at it a bit deeper then

maheini commented 2 years ago

I'm currently trying to simplify the repo, will be finished in 10min (if all goes well).

felangel commented 2 years ago

Hi @maheini 👋 Thanks for opening an issue!

An optional buildWhen can be implemented for more granular control over how often BlocBuilder rebuilds. buildWhen should only be used for performance optimizations as it provides no security about the state passed to the builder function.

https://pub.dev/documentation/flutter_bloc/latest/flutter_bloc/BlocBuilder-class.html

It sounds like you'd benefit from BlocSelector in this case instead of BlocBuilder + buildWhen.

maheini commented 2 years ago

@felangel Oh, no! Exactly described inside the docs, I have even read this part... but not accurate enough. Thanks for looking onto it, @felangel & @Gene-Dana . I'm sorry for opening a already mentioned behaviour.

I will call this issue as solved on my part, maybe as feature-request, but there's no real need right now. Thanks again and have a nice weekend!

Here's my new simpler repo, demonstrating the behaviour (wich behaves as described in the docs): simplified repo