BlocBuilder builder function gets called only once - flutter

Good evening, I have been working with bloc pattern and I have some issues: The state update is being called only once on BlocBuilder, no matter what I do
What I have as state is:
class DateScreenState {
Future<List<PrimaryPetModifierModel>> primaryPetModifiers;
Future<List<SecondaryPetModifierModel>> secondaryPetModifiers;
PrimaryPetModifierModel primaryPetModifierSelected;
SecondaryPetModifierModel secondaryPetModifierSelected;
Widget animatedWidget;
DateTime dateOfSchedule;
DateTime timeOfSchedule;
bool shouldReload = false;
bool isFirstCallAnimation = true;
}
And my mapEventToState looks like this:
#override
Stream<DateScreenState> mapEventToState(
ScheduleDateScreenEvent event) async* {
if (event is SelectPrimaryModifierEvent) {
state.primaryPetModifierSelected = event.modifier;
yield state;
} else if (event is SelectSecondaryModifierEvent) {
state.secondaryPetModifierSelected = event.modifier;
yield state;
}
}
My exact issue is, when I change a value in a DropdownButton, it will fire a SelectedPrimaryModifierEvent or SelectedSecondaryModifier event, the event firing works fine, but the state yielding and updating will happen only once after the first fire of any of those events, after that, BlocBuilder builder function will not be called anymore after any event.

You're yielding the same state with each event, despite the fact that you're changing a variable with the DateScreenState class. Try splitting up your primary and secondary into different state classes and yield them separately in your mapEventToState.

Related

Dart/Flutter Yield state after timer

I am using flutter_bloc and I am not sure how to yield state from within a callback.
Am trying to start timer and return a new state after a pause. Reason I am using a timer is to get the ability to cancel previous timer so it always returns a new state after an idle state.
#override
Stream<VerseState> mapEventToState(
VerseEvent event,
) async* {
if (event is ABCEvent) {
Timer(const Duration(seconds: 3), () {
print("Here");
_onTimerEnd(); // adding yield here returns an error.
})
}
Stream<XYZState> _onTimerEnd() async* {
print("Yielding a state");
yield myNewState();
}
I can see that the code is getting inside the timer callback as I can see the print statements in the callback but not in the timerEnd() method.
State should be yielded by the stream used in bloc that you are current working on. Like
mapEventToState

Flutter bloc adding 2 event in same time

I wanna check users internet connection and firebase auth state changes in my app. I am using flutter bloc for my app's state management. But when call different 2 .add(event) in one initstate always the first one is run and changes states but second one didnt run didnt change state. What is the my wrong ?
my bloc:
class ControllerBloc extends Bloc<ControllerEvent, ControllerState> {
ControllerBloc() : super(ControllerInitial());
AuthApiClient _authApiClient = getIt<AuthApiClient>();
#override
Stream<ControllerState> mapEventToState(
ControllerEvent event,
) async* {
if (event is ControllInternetConnection) {
yield* internetControll();
}
if (event is ControllUserAuth) {
debugPrint("wwwwgeldi");
yield* userAuthControl();
}
// TODO: implement mapEventToState
}
Stream<ControllerState> internetControll() async* {
Stream<DataConnectionStatus> connectionState =
DataConnectionChecker().onStatusChange;
await for (DataConnectionStatus status in connectionState) {
switch (status) {
case DataConnectionStatus.connected:
debugPrint("Bağlandı");
yield InternetConnectedState();
break;
case DataConnectionStatus.disconnected:
debugPrint("Kesildi");
yield InternetConnectionLostState();
break;
}
}
}
Stream<ControllerState> userAuthControl() async* {
FirebaseAuth firebaseAuth = _authApiClient.authInstanceAl();
debugPrint("geldi");
Stream<User> authStream = firebaseAuth.authStateChanges();
_authApiClient.authInstanceAl().signOut();
await for (User authUserResult in authStream) {
if (authUserResult == null) {
yield UserAuthControlError();
}
}
}
}
my page where call my events
class _NavigationPageState extends State<NavigationPage> {
ControllerBloc controllerBloc;
#override
void initState() {
controllerBloc= BlocProvider.of<ControllerBloc>(context);
controllerBloc.add(ControllInternetConnection());
controllerBloc.add(ControllUserAuth());
super.initState();
}
If I am understanding this right, it looks to me that you are trying to solve two different problems with one BLoC. I don't see a reason why internet connection and user auth have to be in one BLoC, rather I would just separate the two in separate BLoCs.
As the discussion in this thread points out, the point of using a BLoC revolves around the idea of predictability purposes. You can override the existing BLoC event stream, but I personally think that is too complicated for what you are trying to do.
So I would suggest, either make two separate BLoCs or combine the entire process into one event, where the internet connection would be checked before the user is authenticated, you will then return different states depending on the errors.

Yield several states in a row but not all are received by the BlocBuilder

I have a BLoC in my app. A View is build upon this BLoC. The View has two main states: let's call them IsGreen and IsRed. Everytime the user taps on a button, the BLoC should switch to another state. So far so fine.
Now I need the the View to display a notification based on a in-between-state. This special state is called IsBlue. If the BLoC switchs from the red state to the green one. During the switch a method is called which can throw an exception or return a result. If the exception is thrown, a notification should be displayed. If the result is valid, show the result.
My mapEventToState looks as follows:
#override
Stream<MyState> mapEventToState(MyEvent event) async* {
switch(event) {
case MyEvent.goRed:
yield IsGreen();
break;
case MyEvent.goGreen:
yield* doSomeStuff();
break;
}
}
Stream<MyState> doSomeStuff() async* {
try {
String result = doSomething();
yield IsBlue(result);
} on MyException {
yield IsException();
}
yield IsGreen();
}
With some logging I found out that the states are properly yielded but the BlocBuilder is not receiving them all. Only the IsGreen and the IsRed events are received. IsBlue and IsException are missed. I don't undestand why. Am I not allowed to send multiple States directly after another?
Does anyone know how often and how fast I can yield state changes?

Flutter - Flutter bloc state not updating

I'm using flutter_bloc library to manage the state of the widgets. I'm trying to update a variable in the state that is a List. When I capture the state and modify the desired value, then yield the new state, the state doesn't update, but the clone of the state is changed.
Stream<ItemState> _updateRestrictions(ItemUpdateRestrictionValue event) async * {
if (state is ItemLoaded) {
final restrictions = (state as ItemLoaded).restrictions;
final newState = restrictions.map((restriction) {
if (restriction.id == event.restrictionId) {
return ItemRestriction(
id: restriction.id,
name: restriction.name,
restrictionType: restriction.restrictionType,
restrictionValues: restriction.restrictionValues,
restrictionValue: event.restrictionValueId
);
}
return restriction;
}).toList();
yield ItemLoaded(restrictions: newState);
}
}
Am I doing something wrong? Or how do you update the state using flutter_bloc correctly?
That probably happens because you use Equatable on your ItemState class and yield the same state back-to-back which is IteamLoaded().
You may wanna read this https://bloclibrary.dev/#/faqs?id=when-to-use-equatable

yielding new state not update UI

I yield same state but with different object in my bloc but BlocBuilder not called again.
How I can do this scenario ?
My mapEventToState is
if (event is EditUserProfileImageChanged) {
UserProfile newUserProfile = state.userProfile;
newUserProfile.avatar = event.imgSrc;
yield EditUserProfileTotalState(userProfile: newUserProfile);
}
When we yield a state in the private mapEventToState handlers, we are always yielding a new state instead of mutating the state. This is because every time we yield, bloc will compare the state to the nextState and will only trigger a state change (transition) if the two states are not equal. If we just mutate and yield the same instance of state, then state == nextState would evaluate to true and no state change would occur.
If you want to change the value of the state, make a copyWith function for your model class.
class UserProfile extends Equatable {
final String name;
final Image avatar;
const UserProfile({this.name, this.avatar});
UserProfile copyWith({String name, Image avatar,}) {
return UserProfile(
name: name ?? this.name,
avatar: avatar?? this.avatar,
);
}
#override
List<Object> get props => [name, avatar];
}
if (event is EditUserProfileImageChanged) {
var newState = state.userProfile.copyWith(avatar: event.imgSrc);
yield EditUserProfileTotalState(userProfile: newState);
}
Removing Equatable solve the problem.
Removing equatbale rebuilds every time even values of the properties are not changed. Instead create new state instance every time.