StreamBuilder TextField does not update its value when changed elsewhere

1,384

I know it's been a long time, but that's my way for solving it.

First, I've created a TextEditingController for my TextField. Then I've created two methods on my BLoC: updateTextOnChanged and updateTextElsewhere. On the fisrt one I just retrieved the value (because I need it to use later). On the second one I added a sink to update the controller on TextField.

Widget:

  return StreamBuilder<String>(
      stream: bloc.streamText,
      builder: (context, snapshot) {
        _controller.text = snapshot.data;
        return Expanded(
            child: TextField(
            controller: _controller,
            onChanged: (value) => {bloc.updateTextOnChanged(value)},
          ),
        );
      }
   );

Bloc:

  Stream<String> get streamText => _controllerTxt.stream;
  String _myText;

  void updateTextElsewhere(String value) {
    _controllerTxt.sink.add(value);
  }

  void updateTextOnChanged(String value) {
    _myText = value;
  }

Then you just need to call updateTextElsewhere() whenever you need to update it outside onChanged.

In you're case just add an empty string like: updateTextElsewhere("");

Share:
1,384
Corey Cole
Author by

Corey Cole

Hello 🙂 I'm a software engineer in Seattle.

Updated on December 10, 2022

Comments

  • Corey Cole
    Corey Cole over 1 year

    I have a reactive login form following the BLOC pattern. I'm trying to programmatically clear all the values in it. In my Bloc, my submit function passes empty strings to my stream sinks:

    class Bloc with Validators {
      final _email = BehaviorSubject<String>();
      final _password = BehaviorSubject<String>();
    
      Stream<String> get email => _email.stream.transform(validateEmail);
      Stream<String> get password => _password.stream.transform(validatePassword);
      Stream<bool> get submitValid => Observable.combineLatest2(email, password, (String e, String p) {
        var valid = (e != null && e.isNotEmpty)
                    && (p != null && p.isNotEmpty);
        print('$e && $p = $valid');
        return valid;
      });
    
      Function(String) get changeEmail => _email.sink.add;
      Function(String) get changePassword => _password.sink.add;
    
      submit() {
        final validEmail = _email.value;
        final validPassword = _email.value;
        print('final values: $validEmail && $validPassword');
        changeEmail('');
        changePassword('');
      }
    
      dispose() {
        _email.close();
        _password.close();
      }
    }
    

    When I press the submit button that calls this submit() function, I get the error messages for both of the text fields, because the values of email and password have changed behind the scenes, but they are not visually updated in the TextFields. Here are my StreamBuilders for my TextFields and Submit button:

    Widget emailField(Bloc bloc) {
        return StreamBuilder(
          stream: bloc.email,
          builder: (context, snapshot) { // re-runs build function every time the stream emits a new value
            return TextField(
              onChanged: bloc.changeEmail,
              autocorrect: false,
              keyboardType: TextInputType.emailAddress,
              decoration: InputDecoration(
                icon: Icon(Icons.email),
                hintText: 'email address ([email protected])',
                labelText: 'Email',
                errorText: snapshot.error
              )
            );
          }
        );
      }
    
      Widget passwordField(Bloc bloc) {
        return StreamBuilder(
          stream: bloc.password,
          builder: (context, AsyncSnapshot<String> snapshot) {
            return TextField(
              onChanged: bloc.changePassword,
              autocorrect: false,
              obscureText: true,
              decoration: InputDecoration(
                icon: Icon(Icons.security),
                hintText: 'must be greater than 6 characters',
                labelText: 'Password',
                errorText: snapshot.error
              )
            );
          }
        );
      }
    
      Widget submitButton(Bloc bloc) {
        return StreamBuilder(
          stream: bloc.submitValid,
          builder: (context, snapshot) {
            return RaisedButton(
              child: Text('Logins'),
              color: Colors.blue,
              onPressed: !snapshot.hasData || snapshot.hasError || snapshot.data == false
                ? null
                : bloc.submit
            );
          }
        );
      }'
    

    And here is the code I'm using for my validators in my Bloc:

    class Validators {
      final validateEmail = StreamTransformer<String, String>.fromHandlers(
        handleData: (email, sink) {
          RegExp exp = new RegExp(r"^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\.[a-zA-Z]+");
          var valid = exp.hasMatch(email);
          if (valid) {
            sink.add(email);
          } else {
            sink.add('');
            sink.addError('Invalid email address!');
          }
        }
      );
    
      final validatePassword = StreamTransformer<String, String>.fromHandlers(
        handleData: (password, sink) {
          var valid = password.length >= 6;
          if (valid) {
            sink.add(password);
          } else {
            sink.add('');
            sink.addError('Password must be at least 6 characters long!');
          }
        }
      );
    }
    

    In my validators, I emit an empty string whenever there is an error. This makes it so the submitValid getter works when the user invalidates something that used to be valid.

  • Corey Cole
    Corey Cole about 5 years
    I don't think this should cause the problem. It should rebuild that UI element with the empty string, but the old value before I passed empty strings to the sinks is still stuck in the UI elements. However, the error message for the UI element is updated to reflect as if the input was empty.