Closed JackBackstack closed 4 years ago
Hi @JackBackstack 👋
Thanks for opening an issue and for the positive feedback! Can you please provide a link to a sample app which illustrates the issue? In general, if you yield too quickly build
will not be called because Flutter renders at 60fps but I would love to take a closer look and ensure it's not an issue in the library 👍
Happy to help you. This small app shows that only the last event triggers the build function. Can't tell if this is Flutter or your Framework.
Single file code
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<SomeBloc>(create: (BuildContext context) => SomeBloc()),
],
child: MaterialApp(
title: "Fast yielding bloc",
home: SomeWidget(),
),
);
}
}
/// WIDGET
class SomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SomeBloc, SomeState>(
buildWhen: (previous, current) {
debugPrint("buildWhen(): state=${current.runtimeType}");
return true;
},
builder: (context, groupState) {
debugPrint("build(): state=${groupState.runtimeType}");
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => generateSomeEvents(context),
),
body: Center(child: Text("Fast yielding bloc")),
);
},
);
}
generateSomeEvents(BuildContext context) async {
for (int i = 0; i < 10; i++) {
if (i.isEven) {
debugPrint("generateSomeEvents(): event value=$i");
BlocProvider.of<SomeBloc>(context).add(SomeEventSomethingOne("even value $i"));
} else {
debugPrint("generateSomeEvents(): event value=$i");
BlocProvider.of<SomeBloc>(context).add(SomeEventSomethingTwo("odd value $i"));
}
}
}
}
/// BLOC
class SomeBloc extends Bloc<SomeEvent, SomeState> {
// constructor
SomeBloc() : super(SomeStateInitial(null));
// mapping
@override
Stream<SomeState> mapEventToState(SomeEvent event) async* {
debugPrint("mapEventToState(): event=${event.runtimeType}");
if (event is SomeEventSomethingOne) {
yield SomeStateSomethingOne(event.eventValue);
} else if (event is SomeEventSomethingTwo) {
yield SomeStateSomethingTwo(event.eventValue);
}
}
}
/// STATE
abstract class SomeState extends Equatable {
final String stateValue;
const SomeState(this.stateValue);
@override
List<Object> get props => [stateValue];
}
class SomeStateInitial extends SomeState {
SomeStateInitial(String stateValue) : super(stateValue);
}
class SomeStateSomethingOne extends SomeState {
SomeStateSomethingOne(String stateValue) : super(stateValue);
}
class SomeStateSomethingTwo extends SomeState {
SomeStateSomethingTwo(String stateValue) : super(stateValue);
}
/// EVENT
abstract class SomeEvent extends Equatable {
final String eventValue;
SomeEvent(this.eventValue);
@override
List<Object> get props => [eventValue];
}
class SomeEventSomethingOne extends SomeEvent {
SomeEventSomethingOne(String eventValue) : super(eventValue);
}
class SomeEventSomethingTwo extends SomeEvent {
SomeEventSomethingTwo(String eventValue) : super(eventValue);
}
Output
I/flutter (22734): generateSomeEvents(): event value=0
I/flutter (22734): generateSomeEvents(): event value=1
I/flutter (22734): generateSomeEvents(): event value=2
I/flutter (22734): generateSomeEvents(): event value=3
I/flutter (22734): generateSomeEvents(): event value=4
I/flutter (22734): generateSomeEvents(): event value=5
I/flutter (22734): generateSomeEvents(): event value=6
I/flutter (22734): generateSomeEvents(): event value=7
I/flutter (22734): generateSomeEvents(): event value=8
I/flutter (22734): generateSomeEvents(): event value=9
I/flutter (22734): mapEventToState(): event=SomeEventSomethingOne
I/flutter (22734): buildWhen(): state=SomeStateSomethingOne
I/flutter (22734): mapEventToState(): event=SomeEventSomethingTwo
I/flutter (22734): buildWhen(): state=SomeStateSomethingTwo
I/flutter (22734): mapEventToState(): event=SomeEventSomethingOne
I/flutter (22734): buildWhen(): state=SomeStateSomethingOne
I/flutter (22734): mapEventToState(): event=SomeEventSomethingTwo
I/flutter (22734): buildWhen(): state=SomeStateSomethingTwo
I/flutter (22734): mapEventToState(): event=SomeEventSomethingOne
I/flutter (22734): buildWhen(): state=SomeStateSomethingOne
I/flutter (22734): mapEventToState(): event=SomeEventSomethingTwo
I/flutter (22734): buildWhen(): state=SomeStateSomethingTwo
I/flutter (22734): mapEventToState(): event=SomeEventSomethingOne
I/flutter (22734): buildWhen(): state=SomeStateSomethingOne
I/flutter (22734): mapEventToState(): event=SomeEventSomethingTwo
I/flutter (22734): buildWhen(): state=SomeStateSomethingTwo
I/flutter (22734): mapEventToState(): event=SomeEventSomethingOne
I/flutter (22734): buildWhen(): state=SomeStateSomethingOne
I/flutter (22734): mapEventToState(): event=SomeEventSomethingTwo
I/flutter (22734): buildWhen(): state=SomeStateSomethingTwo
I/flutter (22734): build(): state=SomeStateSomethingTwo
Nevertheless, do you might have any idea for me how to rely on the build function?
I can confirm that I have similar issue and it happens only with v6.0.3
I have downgraded to v6.0.2
and issue disappears.
@zeusbaba can you please provide a sample app which illustrates the issue? Thanks! 🙏
@zeusbaba @JackBackstack can you try using the following branch and let me know if it addresses the issue? https://github.com/felangel/bloc/pull/1684
@JackBackstack and @zeusbaba I tried the snippet above on both 6.0.3 and 6.0.2 and saw the exact same output. What is the problem you're trying to solve? I can reproduce the same behavior using setState
-- if I call setState
back to back faster than flutter can rebuild you will experience the same behavior.
@felangel This gives the exact output for me. I also can't confirm it was working with any version before.
The problem I want to solve on a higher level: I have a bloc that manages a list of objects which are stored in Firebase's Firestore. So I have an event to create an object and I have an event when backend data changed. Unfortunately the data changed state is fired so fast that the event for successfully creating an object is not visible in the build method. Which I need because this has the ID of the created object.
@felangel clarification after checking with my real project: In this demo snippet it did not change anything. In my real project in fact it changed the state that was passed to the build function. It's the state I wanted in the buildWhen function. That's working as expected. Thanks a lot for this fix! Edit: this seems only to be true for the first build function. If multiple BlocBuilder listening to this state only one of them gets the the desired saved state. All others will get the current state.
Which raises another question. How reliable is this kind of architecture in general? When Flutter skips some build functions without giving us the chance to be aware of it? I assume it's nothing you can do about and many other state managements have similar problems?
let me try to summarize my case;
i'm using
buildWhen: (prev, current) {
return (current is Authenticated || current is Unauthenticated);
}, builder: (context, state) {
CommonUtils.logger.d("main.builder state: $state");
....
to make sure that ONLY those two states triggers rebuild.
However, when used v6.0.3
other states were triggering rebuild as well, and this confused builder
method as it expected only one of the defined states, see buildWhen
.
when i downgraded to v6.0.2
, this issue disappeared.
@zeusbaba @JackBackstack thanks for the feedback! I'm going to have #1684 merged and published to fix the potential incorrect build values when buildWhen
and listenWhen
are provided.
Edit: this seems only to be true for the first build function. If multiple BlocBuilder listening to this state only one of them gets the the desired saved state. All others will get the current state.
Which raises another question. How reliable is this kind of architecture in general? When Flutter skips some build functions without giving us the chance to be aware of it? I assume it's nothing you can do about and many other state managements have similar problems?
Can you provide a sample app to illustrate this issue? I'm not sure I fully understand what you're trying to accomplish, thanks!
@felangel I'm not sure if I manage to create a sample app in the the next days but this is what I'm trying to archieve.
The problem I want to solve on a higher level: I have a bloc that manages a list of objects which are stored in Firebase's Firestore. So I have an event to create an object and I have an event when backend data changed. Unfortunately the data changed state is fired so fast that the event for successfully creating an object is not visible in the build method. Which I need because this has the ID of the created object.
Unfortunately the data changed state is fired so fast that the event for successfully creating an object is not visible in the build method.
@jackbackstack what do you mean by this? All events will be processed by the bloc and will emit states. The issue is states can be emitted more frequently than the UI can re-render but that shouldn't really be a problem and it isn't specific to bloc.
I have same problem.
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => controller,
child: BlocConsumer<GroupFeedViewModel, GroupFeedViewSate>(
listenWhen: (previous, current) {
print("listenWhen: previous: $previous / current: $current");
return current is! SuccessState;
},
listener: (context, state) {
if (state is LoadingState) {
print("listener: LoadingState: ${state.loading}");
context.showHideLoader(state.loading);
} else if (state is ErrorState) {
print("listener: ErrorState");
_builderError();
}
},
buildWhen: (previous, current) {
print("builderWhen: previous: $previous / current: $current");
return current is SuccessState;
},
builder: (context, state) {
print("builder: $state");
if (state is SuccessState) {
return _buildSuccessState();
}
return SizedBox.shrink();
},
));
}
Log:
builder: Instance of 'LoadingState'
I make first call in init:
void initState() {
super.initState();
controller.init(widget.groupId);
}
My condition not accept state different than SuccessState
, but when open screen and make first call, builder is calling.
This is very fast and is valid for the first request, the others work as expected.
bloc: ^6.1.1
Any idea?
Hi @thalissone please refer to the documentation:
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
This is working as expected and your builder should be able to handle every state -- buildWhen
is just an optimization. Hope that helps 👍
First of all: thanks for this amazing library :)
More importantly: I think the builder doesn't take the state that was considered in the buildWhen method when the states are yielding too fast. I would expect to get the state in the builder method that I checked in the buildWhen method and not the latest state.
BlocBuilder Code
Output
Or do you have any other suggestion for this issue?