页面重新加载后Flutter StreamBuilder无法正常运行

时间:2019-03-13 00:45:09

标签: flutter flutter-layout stream-builder

我的应用程序中有一个带有按钮的TextField Form Validators注册页面。如果未满足业务规则,则文本字段将显示表单验证错误消息,并且一旦满足所有条件,“下一步”按钮将变为可点击状态。目前,这一切在我的应用程序中都运行良好,但是我发现一旦离开页面并返回页面,验证错误消息就会停止显示,并且按钮也将停止工作。在我的IDE(android studio)中查看控制台日志,我得到的唯一相关错误消息是

[VERBOSE-2:ui_dart_state.cc(148)] Unhandled Exception: Bad state: 
Stream is already closed
#0      _SinkTransformerStreamSubscription._add 
(dart:async/stream_transformers.dart:66:7)
#1      _EventSinkWrapper.add 
(dart:async/stream_transformers.dart:15:11)

我不确定这是什么意思,页面重新加载后,流会关闭而不重新打开吗?如果没有,有没有办法我可以解决这个问题,或者我缺少什么? This is我正在经历

流生成器代码:

    Widget emailField(authBloc) {
    return StreamBuilder(
      stream: authBloc.emailStream,
      builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
        return TextField(
          onChanged: authBloc.updateEmail,
          keyboardType: TextInputType.emailAddress,
          decoration: InputDecoration(
              border: UnderlineInputBorder(
                borderSide: BorderSide(
                  color: Colors.deepOrange
                )
              ),
              hintText: 'Enter Email',
              labelText: 'Email Address',
              errorText: snapshot.error
          ),
        );
      },
    );
  }

  Widget passwordField( authBloc) {
    return StreamBuilder(
      stream: authBloc.passwordStream,
      builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
        return TextField(
          onChanged: authBloc.updatePassword,
          obscureText: true,
          decoration: InputDecoration(
            hintText: 'Enter password',
            labelText: 'Password',
            errorText: snapshot.error,
          ),
        );
      },
    );
  }


  Widget checkPasswordField( authBloc) {
    return StreamBuilder(
      stream: authBloc.validatePasswordStream,
      builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
        return TextField(
          onChanged: authBloc.updateValidatePassword,
          obscureText: true,
          decoration: InputDecoration(
            hintText: 'Re-enter password',
            labelText: 'Confirm Password',
            errorText: snapshot.error,
          ),
        );
      },
    );
  }

  Widget nextBtn(authBloc) {
    return StreamBuilder(
        stream: authBloc.submitValid,
        builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
          return RaisedButton(
            child: Text('Next'),
            color: Colors.deepOrange,
            shape: BeveledRectangleBorder(
              borderRadius: BorderRadius.all(Radius.circular(7.0))
            ),
            onPressed: snapshot.hasData
                ? () => Navigator.pushNamed(context, '/register')
            : null,
          );
        }
    );
  }

流:

       /// REGISTER VARIABLES
  static final _emailController = BehaviorSubject<
      String>(); //RxDart's implementation of StreamController. Broadcast stream by default
  static final _passwordController = BehaviorSubject<String>();
  static final _validatePasswordController = BehaviorSubject<
      String>(); // Will check that the password entered the 2nd time is correct

 /// REGISTER STREAM & METHODS
  //Retrieve data from the stream
  Stream<String> get emailStream => _emailController.stream
      .transform(performEmailValidation); //Return the transformed stream

  Stream<String> get passwordStream =>
      _passwordController.stream.transform(performPasswordValidation);

  Stream<String> get validatePasswordStream =>
      _validatePasswordController.stream.transform(performIsPasswordSame);

 //Merging email, password and validate password
  Stream<bool> get submitValid => Observable.combineLatest3(
      emailStream, passwordStream, validatePasswordStream, (e, p1, p2) => true);

//Add data to the stream
  Function(String) get updateEmail => _emailController.sink.add;
  Function(String) get updatePassword => _passwordController.sink.add;
  Function(String) get updateValidatePassword =>
      _validatePasswordController.sink.add;

// performing user input validations
  final performEmailValidation = StreamTransformer<String, String>.fromHandlers(
      handleData: (email, sink) async {
    String emailValidationRule =
        r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
    RegExp regExp = new RegExp(emailValidationRule);
    if (await doesNameAlreadyExist("email", _emailController.value) == true)
      sink.addError("That email already exists");
    else if (regExp.hasMatch(email)) {
      sink.add(email);
    } else {
      sink.addError(StringConstant.emailErrorMessage);
    }
  });

  final performPasswordValidation =
      StreamTransformer<String, String>.fromHandlers(
          handleData: (_passwordController, sink) {
    if (_passwordController.length >= 6) {
      sink.add(_passwordController);
    } else {
      sink.addError(StringConstant.passwordErrorMessage);
    }
  });

  final performIsPasswordSame = StreamTransformer<String, String>.fromHandlers(
      handleData: (password, sink) {
    if (password != _passwordController.value)
      sink.addError(StringConstant.invalidPasswordMessage);
    else
      sink.add(password);
  });

整个屏幕代码:

class SignUp extends StatefulWidget {
  @override
  _SignUpState createState() => _SignUpState();
}



class _SignUpState extends State<SignUp> {
  AuthBloc _authBloc;



  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _authBloc = AuthBlocProvider.of(context);
  }




  @override
  Widget build(BuildContext context) {


    return Scaffold(

      body: Container(
          alignment: Alignment.center,
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              children: <Widget>[
                SizedBox(height: 80,),
                Text("Register", style: Style.appTextStyle),
                SizedBox(height: 100,),
                emailField(_authBloc),
                SizedBox(height: 30),
                passwordField(_authBloc),
                SizedBox(height: 30),
                checkPasswordField(_authBloc),
                SizedBox(height: 30),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: <Widget>[
                    cancelBtn(),

                    nextBtn(_authBloc),

                  ],
                )

                // checkPasswordField(authBloc),
              ],
            ),
          )
      ),
    );
  }

  Widget emailField(authBloc) {
    return StreamBuilder(
      stream: authBloc.emailStream,
      builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
        return TextField(
          onChanged: authBloc.updateEmail,
          keyboardType: TextInputType.emailAddress,
          decoration: InputDecoration(
              border: UnderlineInputBorder(
                borderSide: BorderSide(
                  color: Colors.deepOrange
                )
              ),
              hintText: 'Enter Email',
              labelText: 'Email Address',
              errorText: snapshot.error
          ),
        );
      },
    );
  }

  Widget passwordField( authBloc) {
    return StreamBuilder(
      stream: authBloc.passwordStream,
      builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
        return TextField(
          onChanged: authBloc.updatePassword,
          obscureText: true,
          decoration: InputDecoration(
            hintText: 'Enter password',
            labelText: 'Password',
            errorText: snapshot.error,
          ),
        );
      },
    );
  }


  Widget checkPasswordField( authBloc) {
    return StreamBuilder(
      stream: authBloc.validatePasswordStream,
      builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
        return TextField(
          onChanged: authBloc.updateValidatePassword,
          obscureText: true,
          decoration: InputDecoration(
            hintText: 'Re-enter password',
            labelText: 'Confirm Password',
            errorText: snapshot.error,
          ),
        );
      },
    );
  }

  Widget nextBtn(authBloc) {
    return StreamBuilder(
        stream: authBloc.submitValid,
        builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
          return RaisedButton(
            child: Text('Next'),
            color: Colors.deepOrange,
            shape: BeveledRectangleBorder(
              borderRadius: BorderRadius.all(Radius.circular(7.0))
            ),
            onPressed: snapshot.hasData
                ? () => Navigator.pushNamed(context, '/register')
            : null,
          );
        }
    );
  }

  Widget cancelBtn(){
    return RaisedButton(
      child: Text('Cancel'),
      color: Colors.white30,
      shape: BeveledRectangleBorder(
        borderRadius: BorderRadius.all(Radius.circular(7.0))
      ),
      onPressed: () => Navigator.pop(context),
    );
  }

  @override
  void dispose() {
    super.dispose();
    _authBloc.dispose();
  }

集团代码:

/// REGISTER VARIABLES
      static final _emailController = BehaviorSubject<
          String>(); //RxDart's implementation of StreamController. Broadcast stream by default
      static final _passwordController = BehaviorSubject<String>();
      static final _validatePasswordController = BehaviorSubject<
          String>(); // Will check that the password entered the 2nd time is correct

     /// REGISTER STREAM & METHODS
      //Retrieve data from the stream
      Stream<String> get emailStream => _emailController.stream
          .transform(performEmailValidation); //Return the transformed stream

      Stream<String> get passwordStream =>
          _passwordController.stream.transform(performPasswordValidation);

      Stream<String> get validatePasswordStream =>
          _validatePasswordController.stream.transform(performIsPasswordSame);

     //Merging email, password and validate password
      Stream<bool> get submitValid => Observable.combineLatest3(
          emailStream, passwordStream, validatePasswordStream, (e, p1, p2) => true);

    //Add data to the stream
      Function(String) get updateEmail => _emailController.sink.add;
      Function(String) get updatePassword => _passwordController.sink.add;
      Function(String) get updateValidatePassword =>
          _validatePasswordController.sink.add;

    // performing user input validations
      final performEmailValidation = StreamTransformer<String, String>.fromHandlers(
          handleData: (email, sink) async {
        String emailValidationRule =
            r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
        RegExp regExp = new RegExp(emailValidationRule);
        if (await doesNameAlreadyExist("email", _emailController.value) == true)
          sink.addError("That email already exists");
        else if (regExp.hasMatch(email)) {
          sink.add(email);
        } else {
          sink.addError(StringConstant.emailErrorMessage);
        }
      });

      final performPasswordValidation =
          StreamTransformer<String, String>.fromHandlers(
              handleData: (_passwordController, sink) {
        if (_passwordController.length >= 6) {
          sink.add(_passwordController);
        } else {
          sink.addError(StringConstant.passwordErrorMessage);
        }
      });

      final performIsPasswordSame = StreamTransformer<String, String>.fromHandlers(
          handleData: (password, sink) {
        if (password != _passwordController.value)
          sink.addError(StringConstant.invalidPasswordMessage);
        else
          sink.add(password);
      });

    dispose() {
        _emailController.close();
        _passwordController.close();
        _validatePasswordController.close();

    }

1 个答案:

答案 0 :(得分:1)

好吧,寻找完整的源代码和您显示的GIF,我可以看到导致此问题的原因。 您的错误是在dispose() SingUp小部件类方法中调用dispose() BLoC实例方法。

为什么会出错?

在特定情况下,当您在SingUp屏幕中并转到下一个路由/屏幕时,将调用SingUp中的dispose方法,此刻将关闭BLoC实例的流。但是下一个允许用户返回到SingUp屏幕,当发生这种情况时,SingUp实例将获得与以前使用的相同的BLoC实例,但是此BLoC实例已关闭流。

我如何以简单的方式解决这个问题?

在SingUp类中:

@override
  void dispose() {
    super.dispose();
   /// DON'T CALL BLoC dispose here
   /// _authBloc.dispose();
  }

请勿在此处丢弃BloC,因为用户可以随时返回此屏幕。由于您使用InheritedWidget来获取BLoC实例,这使您可以在不同位置访问同一BLoC实例,因此我建议您在用户结束所有提示过程时致电yourBloc.dispose()