Flutter StreamBuilder在页面重新加载后无法正常工作

问题描述 投票:0回答:1

我的应用程序中有一个带有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我正在经历的事情

Stream Builder代码:

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

    }
flutter flutter-layout stream-builder
1个回答
1
投票

好吧,期待完整的来源和你展示的GIF,我可以看到是什么导致了这个问题。您的错误在qazxsw poi SignUp小部件类方法中称为qazxsw poi BLoC实例方法。

为什么会出错?

在您的特定情况下,当您进入SingUp屏幕并转到下一个路径/屏幕时,将调用SingUp的dispose()方法,此时BLoC实例的流将被关闭。但是接下来允许用户返回SingUp屏幕,当发生这种情况时,SingUp实例获得之前使用过的BLoC实例,但是这个BLoC实例已经关闭了流。

我怎样才能以简单的方式解决这个问题?

在SingUp课程中:

dispose()

请勿在此处置BloC,因为用户可以随时返回此屏幕。由于您正在使用InheritedWidget来获取BLoC实例,这使您可以访问不同位置的相同BLoC实例,我建议您在用户结束所有启动过程的时刻调用dispose

© www.soinside.com 2019 - 2024. All rights reserved.