Bloc: Allow transition to the new state if current and next states are equals

Created on 6 Jun 2019  路  8Comments  路  Source: felangel/bloc

Assume this scenario:

  • User tap on a button to send his phone number
  • mapEventToState will only yield InvalidPhoneNumberFormat() if the phone number has an invalid format.
  • UI will be notified only on the first emission, because of if (currentState == nextState || _stateSubject.isClosed) return;

Here are two options to fix this issue:

  • Remove equatable from base state which breaks all expectLater(bloc.state, emitsInOrder(states));.
  • Override == 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.

question

Most helpful comment

Yes, I actually do that, Thank you for your time.

All 8 comments

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,

  • A user types his password and sends it
  • bloc waits for the response from the server
  • Assume that the response is: Error('Inavlid_Passowrd)`
  • So bloc dispatch Error('invalid_password)`
  • So it's fine, inside the listener, I will show a new snackbar

But 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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

craiglabenz picture craiglabenz  路  3Comments

nhwilly picture nhwilly  路  3Comments

krusek picture krusek  路  3Comments

wheel1992 picture wheel1992  路  3Comments

komapeb picture komapeb  路  3Comments