页面重新加载后 Flutter StreamBuilder 不工作

Flutter StreamBuilder not working after Page Reload

我的应用程序中有一个带有按钮的带有 TextField 表单验证器的注册页面。如果未满足业务规则,文本字段将显示表单验证错误消息,一旦满足所有条件,"next" 按钮将可点击。这在我的应用程序中目前一切正常,但我发现一旦我离开页面并 return 到它,验证错误消息停止显示并且按钮也停止工作。查看我的 IDE(android 工作室)中的控制台日志,我收到的唯一相关错误消息是

[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();

    }

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

为什么是错误的?

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

如何简单地解决这个问题?

在 SingUp class :

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

不要在此处放置您的 BloC,因为用户可以随时返回此屏幕。由于您正在使用 InheritedWidget 获取 BLoC 实例,这使您可以在不同的地方访问相同的 BLoC 实例,我建议您在用户结束所有注册过程时调用 yourBloc.dispose()