Rx.combineLatest2 没有按预期工作

Rx.combineLatest2 not working as expected

我是 flutter 的新手。我尝试在 flutter 中创建一些示例应用程序以创建自己的应用程序。我在 flutter with bloc pattern 中找到了一个不错的应用。请看以下link

这只是一个登录页面。表单中有一个带有验证的电子邮件和密码文本框。一个用于电子邮件的验证器和另一个用于密码长度的验证器。表单中有一个提交按钮,最初它是禁用的,如果电子邮件和密码成功验证,则提交按钮启用。它使用带有 rxdart 包的 bloc 模式架构。

我在提交按钮验证中遇到问题,在输入电子邮件和密码字段后未启用按钮字段。

按钮验证码

Stream<bool> get submitCheck => 
  Rx.combineLatest2(email, password, (e, p) => true);

请看下面的代码。

main.dart

import 'package:bloc_login/pagetwo.dart';
import 'package:flutter/material.dart';
import 'package:bloc_login/bloc.dart';
import 'package:flutter/rendering.dart';

 void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
 @override
  Widget build(BuildContext context) {
return MaterialApp(
  home: MyHomePage(title: 'Flutter Demo Home Page'),
  title: 'Flutter Demo',
  theme: ThemeData(
    primarySwatch: Colors.teal,
  ),
  debugShowCheckedModeBanner: false,
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
 @override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
 changethePage(BuildContext context) {
Navigator.of(context)
    .push(MaterialPageRoute(builder: (context) => PageTwo()));
  }

 @override
   Widget build(BuildContext context) {
final bloc = Bloc();

return Scaffold(
  appBar: AppBar(
    title: Text("Bloc pattern"),
  ),
  body: SingleChildScrollView(
    child: Container(
      height: MediaQuery.of(context).size.height,
      padding: EdgeInsets.all(16),
      child: Column(
        mainAxisSize: MainAxisSize.max,
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          StreamBuilder<String>(
            stream: bloc.email,
            builder: (context, snapshot) => TextField(
              onChanged: bloc.emailChanged,
              keyboardType: TextInputType.emailAddress,
              decoration: InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: "Enter Email",
                  labelText: "Email",
                  errorText: snapshot.error),
            ),
          ),
          SizedBox(
            height: 20,
          ),
          StreamBuilder<String>(
            stream: bloc.password,
            builder: (context, snapshot) => TextField(
              onChanged: bloc.passwordChanged,
              keyboardType: TextInputType.text,
              obscureText: true,
              decoration: InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: "Enter password",
                  labelText: "Password",
                  errorText: snapshot.error),
            ),
          ),
          SizedBox(
            height: 20,
          ),
          StreamBuilder<bool>(
              stream: bloc.submitCheck,
              builder: (context, snapshot) => RaisedButton(
                    color: Colors.tealAccent,
                    onPressed: (snapshot.data != null)
                        ? () => changethePage(context)
                        : null,
                    child: Text("Submit"),
                  ))
        ],
      ),
    ),
  ),
);
}
}

bloc.dart

import 'dart:async';
import 'package:bloc_login/validator.dart';
import 'package:rxdart/rxdart.dart';

class Bloc extends Object with Validators implements BaseBloc {
final _emailController = StreamController<String>();
final _passwordController = StreamController<String>();

Function(String) get emailChanged => _emailController.sink.add;
Function(String) get passwordChanged => _passwordController.sink.add;

Stream<String> get email => _emailController.stream.transform(emailValidator);
Stream<String> get password =>
  _passwordController.stream.transform(passwordValidator);

Stream<bool> get submitCheck =>
  Rx.combineLatest2(email, password, (e, p) => true);

@override
void dispose() {
_emailController.close();
_passwordController.close();
}
}

 abstract class BaseBloc {
   void dispose();
}

validator.dart

import 'dart:async';

mixin Validators {
 var emailValidator =
  StreamTransformer<String, String>.fromHandlers(handleData: (email, sink) {
if (email.contains("@")) {
  sink.add(email);
} else {
  sink.addError("Email is not valid.");
}
});

var passwordValidator = StreamTransformer<String, String>.fromHandlers(
  handleData: (password, sink) {
if (password.length > 4) {
  sink.add(password);
} else {
  sink.addError("Password length should be greater than 4.");
}
});
}

请指教

Combinelatest2 很好,问题是您在每次重建中都创建了一个新的 bloc。

因此在 StatefulWidgetinitState method and dispose the bloc in the dispose 方法中创建 bloc。

但是您的 StreamControllers 也有一个 Stream 只支持一个订阅者,所以如果您希望 StreamControllerStream 可以被多次收听,这个需要是广播流,其中一种方法是使用 StreamController .broadcast () 构造函数。

P.D。如果您使用 bloc 模式创建表单,您可以检查 flutter_form_bloc,它可以节省大量代码。


从 dart 2.1 开始 you don't need to extend an object to use a mixin

class Bloc with Validators implements BaseBloc {
  final _emailController = StreamController<String>.broadcast();
  final _passwordController = StreamController<String>.broadcast();
  //...
}
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Bloc _bloc;

  @override
  void initState() {
    super.initState();
    _bloc = Bloc();
  }

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

  changethePage(BuildContext context) {
    Navigator.of(context)
        .push(MaterialPageRoute(builder: (context) => PageTwo()));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Bloc pattern"),
      ),
      body: SingleChildScrollView(
        child: Container(
          height: MediaQuery.of(context).size.height,
          padding: EdgeInsets.all(16),
          child: Column(
            mainAxisSize: MainAxisSize.max,
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              StreamBuilder<String>(
                stream: _bloc.email,
                builder: (context, snapshot) => TextField(
                  onChanged: _bloc.emailChanged,
                  keyboardType: TextInputType.emailAddress,
                  decoration: InputDecoration(
                      border: OutlineInputBorder(),
                      hintText: "Enter Email",
                      labelText: "Email",
                      errorText: snapshot.error),
                ),
              ),
              SizedBox(
                height: 20,
              ),
              StreamBuilder<String>(
                stream: _bloc.password,
                builder: (context, snapshot) => TextField(
                  onChanged: _bloc.passwordChanged,
                  keyboardType: TextInputType.text,
                  obscureText: true,
                  decoration: InputDecoration(
                      border: OutlineInputBorder(),
                      hintText: "Enter password",
                      labelText: "Password",
                      errorText: snapshot.error),
                ),
              ),
              SizedBox(
                height: 20,
              ),
              StreamBuilder<bool>(
                  stream: _bloc.submitCheck,
                  builder: (context, snapshot) => RaisedButton(
                        color: Colors.tealAccent,
                        onPressed: (snapshot.data != null)
                            ? () => changethePage(context)
                            : null,
                        child: Text("Submit"),
                      ))
            ],
          ),
        ),
      ),
    );
  }
}