Failed to update the state using BlockBuilder() and TextField()'s onChanged property. [Flutter_Bloc]

Issue

I am trying to do a simple mockup of form validation, and so far I was not able to update my State under the BlocBuilder() using the TextField()’s onChanged property.

Here’s my BlocProvider()

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Log me in',
      home: Scaffold(
        body: BlocProvider<LoginBloc>(
          create: (context) => LoginBloc(),
          child: LoginScreen(),
        ),
      ),
    );
  }
}

Here’s the main file that needs to be updated on input changes.
The ‘$snapshot’ supposed to yields the what’s coming from the LoginBloc depending on its’ current state, but looks like it doesn’t rebuild itself onChanged online StreamBuilder()

lass LoginScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final LoginBloc bloc = BlocProvider.of<LoginBloc>(context);
    return BlocBuilder<LoginBloc, String>(
        bloc: LoginBloc(),
        builder: (context, snapshot) {
          return Container(
            margin: EdgeInsets.all(10),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                // emailField(bloc),
                TextField(
                  onChanged: (_) => bloc.add(LoginEvent.username),
                  keyboardType: TextInputType.emailAddress,
                  decoration: InputDecoration(
                    hintText: 'example@email.com',
                    labelText: 'Email Address',
                    errorText: '$snapshot',
                  ),
                ),
                // passwordField(bloc),
                TextField(
                  onChanged: (_) => bloc.add(LoginEvent.password),
                  keyboardType: TextInputType.visiblePassword,
                  decoration: InputDecoration(
                    hintText: 'Password',
                    labelText: 'Password',
                    errorText: '$snapshot',
                  ),
                ),
                SizedBox(
                  height: 25,
                ),
                // submitButton(),
              ],
            ),
          );
        });
  }

Here’s my LoginBloc()

class LoginBloc extends Bloc<LoginEvent, String> {
  get initialState => '';

  Stream<String> mapEventToState(LoginEvent event) async* {
    switch (event) {
      case LoginEvent.username:
        if (state.contains('@')) {
          yield 'Approved';
        } else {
          yield 'Please retry';
        }
        break;
      case LoginEvent.password:
        if (state.length > 3) {
          yield 'Nice password';
        }
        yield "Please retry";
    }
  }
}

Thank you

Solution

There are two different instances of LoginBloc in your app. The one created and passed by the BlocProvider and the second which you have created inside the BlocBuilder.

The problem is that you are adding events into the BlocProvider's bloc and the BlocBuilder is listening to the another one.

To fix this you can do like this

Widget build(BuildContext context) {
  final LoginBloc bloc = BlocProvider.of<LoginBloc>(context);
  return BlocBuilder<LoginBloc, String>(
    bloc: bloc,      // Pass the above bloc instance 
    builder: (context, snapshot) {
      // rest as it is
    }
  )
}

Bonus:- You can skip the bloc: bloc as it is an optional argument and if not provided it finds automatically from the tree using BlocProvider

You also need to manage state differently otherwise typing in one of the field will show error in both of them

Answered By – Shubham Tanwar

Answer Checked By – Marilyn (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *