Assume this scenario:
mapEventToState will only yield InvalidPhoneNumberFormat() if the phone number has an invalid format.if (currentState == nextState || _stateSubject.isClosed) return; Here are two options to fix this issue:
equatable from base state which breaks all expectLater(bloc.state, emitsInOrder(states));.== to always return false but this time only related test to the InvalidPhoneNumberFormat will be break, so it's better than the previous option.Is there any better approach to achieve this scenario? I think it's a common use-case, for example, in form validation.
Hi @SaeedMasoumi 馃憢
Thanks for opening an issue!
Regarding your question, why would you want your BlocBuilder to rebuild if the state hasn't changed?
If you yield InvalidPhoneNumberFormat 10 times, it鈥檚 inefficient to rebuild the widgets 10 times for no reason.
For example, If you want to show error-hint for your TextField you need to rebuild it. So, It's not inefficient, If BlocBuilder only wraps the TextField.
Also, the BlocListener didn't call listener too, I need to show a SnackBar.
@SaeedMasoumi can you please provide a link to a sample application which illustrates the problem you're having?
Yes, of course.
Here is the Widget:
var kCounter = 1;
class MyWidget extends StatelessWidget {
final _bloc = MyBloc();
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider(
bloc: _bloc,
child: BlocListener(
bloc: _bloc,
listener: (context, state) {
if (state is InvalidFormat) {
Alert.error(context, description: 'error');
}
},
child: BlocBuilder(
bloc: _bloc,
builder: (_, state) => RaisedButton(
child: Text('${kCounter++}'),
onPressed: () {
_bloc.dispatch(Perform());
},
)),
),
),
);
}
}
And Bloc classes:
// my_bloc.dart
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
@immutable
abstract class MyBlocState extends Equatable {
MyBlocState([List props = const []]) : super(props);
}
class Initial extends MyBlocState {}
class InvalidFormat extends MyBlocState {}
// my_bloc_event.dart
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
@immutable
abstract class MyBlocEvent extends Equatable {
MyBlocEvent([List props = const []]) : super(props);
}
class Perform extends MyBlocEvent {}
// my_bloc_state.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import './bloc.dart';
class MyBloc extends Bloc<MyBlocEvent, MyBlocState> {
@override
MyBlocState get initialState => Initial();
@override
Stream<MyBlocState> mapEventToState(
MyBlocEvent event,
) async* {
yield InvalidFormat();
}
}
So the problem is, neither listener nor builder gets called after the second emission of InvalidFormat.
Hi @SaeedMasoumi I still don't understand why you would want them to be called again if nothing has changed. In the example you provided, once the bloc yields InvalidFormat and the BlocBuilder and BlocListener are called, there is no reason for them to ever be called again because the state of the bloc can never change. Do you have a real world example of why you would want this behavior?
Can you provide a realistic sample application where you'd want everything to re-build even if the state hasn't changed? Thanks!
For builder you're right, there is no need to rebuild the widget but for listener.
Here is our real world use-case,
bloc waits for the response from the serverError('Inavlid_Passowrd)`Error('invalid_password)`listener, I will show a new snackbarBut user types his password again / or maybe just tap on send button again without any changes. Again we need to show a newsnackbar, but we can't.
@SaeedMasoumi thanks for clarifying! In that case I would not recommend having your state extend Equatable since you want different instances of the state to be treated as different states. You can update your tests to include matchers in emitsInOrder.
Closing for now but feel free to comment with additional questions and I鈥檓 happy to continue the conversation. 馃憤
Yes, I actually do that, Thank you for your time.
Most helpful comment
Yes, I actually do that, Thank you for your time.