【问题标题】:Flutter StreamBuilder not working after Page Reload页面重新加载后 Flutter StreamBuilder 无法正常工作
【发布时间】:2019-08-03 14:09:47
【问题描述】:

我的应用程序中有一个带有 TextField 表单验证器的注册页面,并带有一个按钮。如果未满足业务规则,文本字段将显示表单验证错误消息,并且一旦满足所有条件,“下一步”按钮将可点击。这目前在我的应用程序中运行良好,但我发现一旦我离开页面并返回它,验证错误消息就会停止显示,并且按钮也会停止工作。在我的 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();

    }

【问题讨论】:

  • 我需要了解您在何处以及如何创建和处置 BLoC 实例。如果可能,请分享整个屏幕代码和 BLoC。但正如消息错误所说“Stream is already closed”意味着您可能正在关闭 BLoC 流,当您走下一条路线时,当您返回“FORM SCREEN”时,您正在使用相同的BLoC 实例,但所有流都已关闭。你是用 InheritedWidget 来获取 Bloc 吗?
  • 我用初始化和关闭 bloc 实例的代码更新了我的问题。一旦用户离开并返回页面,我如何使用另一个 BLoC 实例?是的,我正在使用 inheritWidget 来获取 Bloc
  • 您应该使用与您已经使用的相同的 BLoC 实例。

标签: flutter flutter-layout stream-builder


【解决方案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()

【讨论】:

  • 哦,感谢您的明确解释。假设用户意外取消注册并决定返回,我将如何初始化 bloc 的另一个实例
  • 要做到这一点,您不需要 InheritedWidget 来创建您的 BLoC。您只需在 SingUp 类中使用 YouBlocConstructor 创建一个 BLoC 实例,并将此 bloc 实例作为 SingUp 状态类的成员。但请注意,当用户出于某种原因返回 SingUp 屏幕时,创建和使用新的 Bloc 实例可能会使您丢失以前的用户数据。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多