如何使用Bloc在Flutter中管理条件渲染,而不会出现“布尔表达式不能为空”错误?

时间:2019-05-02 14:16:16

标签: dart flutter bloc rxdart

我只是在尝试抖动,我似乎无法使组件根据我的Bloc中的BehaviourStream有条件地进行渲染。

我希望最初显示“ _buildPage()”小部件(这是身份验证表单),然后虽然_isLoading为true,但(_loginSucceded为false),但我希望显示微调器。最后,当_loginSucceded为true且_isLoading为false时,我希望重定向用户。

实际行为是一旦提交表单,加载程序就会按预期显示。从服务器成功收到响应后,将再次呈现的身份验证。

我认为我的逻辑很好,但是似乎当我在Bloc构造函数中设置流的值时,其他原因导致应用重新渲染,从而导致流中的值为空。

有没有一种方法可以确保在构造函数初始化后流始终具有基本值?

还是有更好的方法来管理这种情况?我才刚刚开始从JS背景开始研究Flutter,所以我很可能会错过一些东西。

集团代码:

import 'dart:async';
import 'dart:convert';
import 'package:rxdart/rxdart.dart';
import 'package:http/http.dart' as http;
import './auth_validator.dart';

class AuthBloc with AuthValidator {
  final _email = BehaviorSubject<String>();
  final _password = BehaviorSubject<String>();
  final _isLoading = BehaviorSubject<bool>();
  final _loginSucceded = BehaviorSubject<bool>();

  AuthBloc() {
    _isLoading.sink.add(false);
    _loginSucceded.sink.add(false);
  }

  // stream getters
  Stream<String> get email => _email.stream.transform(validateEmail);
  Stream<String> get password => _password.stream.transform(validatePassword);
  Stream<bool> get isLoading => _isLoading.stream;
  Stream<bool> get loginSuccess => _loginSucceded.stream;
  Stream<bool> get submitValid =>
      Observable.combineLatest2(email, password, (e, p) => true);

  // add data to sink onChange
  Function(String) get emailChanged => _email.sink.add;
  Function(String) get passwordChanged => _password.sink.add;

  void submitForm() async {
    try {
      final Map user = {'email': _email.value, 'password': _password.value};
      final jsonUser = json.encode(user);

      _isLoading.sink.add(true);

      // submit to server
      final http.Response response = await http.post(
        'http://192.168.1.213:5000/api/users/signin',
        body: jsonUser,
        headers: {'Content-Type': 'application/json'},
      );

      final Map<String, dynamic> decodedRes = await json.decode(response.body);

        _isLoading.sink.add(false);
        _loginSucceded.sink.add(true);

      void dispose() {
        _email.close();
        _password.close();
        _isLoading.close();
        _loginSucceded.close();
      }
    } catch (e) {
      print('error: $e');
      _isLoading.sink.add(false);
    }
  }
}

小部件代码:

import 'package:flutter/material.dart';
import '../blocs/auth_bloc.dart';

class LoginPage extends StatelessWidget {
  final authBloc = AuthBloc();

  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      stream: authBloc.loginSuccess,
      builder: (context, snapshot1) {
        return StreamBuilder(
          stream: authBloc.isLoading,
          builder: (context, snapshot2) {
            print('loginSuccess? ${snapshot1.data} isLoading? ${snapshot2.data}');
            return Scaffold(
                body: !snapshot1.data && snapshot2.data
                    ? _circularSpinner()
                    : snapshot1.data && snapshot2.data
                        ? Navigator.pushReplacementNamed(context, '/dashboard')
                        : _buildPage());
          },
        );
      },
    );
  }

  Widget _buildPage() {
    return Container(
      margin: EdgeInsets.all(20.0),
      child: Center(
        child: SingleChildScrollView(
          child: Column(
            children: <Widget>[
              _emailField(authBloc),
              _padding(),
              _passwordField(authBloc),
              _padding(),
              _submitButton(authBloc)
            ],
          ),
        ),
      ),
    );
  }

  Widget _circularSpinner() {
    return Center(
      child: CircularProgressIndicator(),
    );
  }

  Widget _emailField(AuthBloc authBloc) {
    return StreamBuilder(
      stream: authBloc.email,
      builder: (BuildContext context, snapshot) {
        return TextField(
          onChanged: authBloc.emailChanged,
          keyboardType: TextInputType.emailAddress,
          decoration: InputDecoration(
            hintText: 'you@example.com',
            labelText: 'Email Address',
            errorText: snapshot.error,
            border: OutlineInputBorder(),
          ),
        );
      },
    );
  }

  Widget _passwordField(AuthBloc authBloc) {
    return StreamBuilder(
      stream: authBloc.password,
      builder: (BuildContext context, snapshot) {
        return TextField(
          onChanged: authBloc.passwordChanged,
          obscureText: true,
          keyboardType: TextInputType.emailAddress,
          decoration: InputDecoration(
            hintText: '8 characters or more with at least 1 number',
            labelText: 'Password',
            errorText: snapshot.error,
            border: OutlineInputBorder(),
          ),
        );
      },
    );
  }

  Widget _padding() {
    return Padding(
      padding: EdgeInsets.only(top: 20.0),
    );
  }

  Widget _submitButton(AuthBloc authBloc) {
    return StreamBuilder(
        stream: authBloc.submitValid,
        builder: (context, snapshot) {
          return RaisedButton(
            child: Text('Login'),
            color: Colors.blue,
            onPressed: snapshot.hasError ? null : authBloc.submitForm,
          );
        });
  }
}

main.dart

import 'package:flutter/material.dart';
import './app.dart';
import 'package:flutter/material.dart';
import './pages/auth.dart';
import './pages/dashboard.dart';

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

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {
        '/': (BuildContext context) => LoginPage(),
        '/dashboard': (BuildContext context) => DashBoardPage(),
      },
    );
  }
}

日志

Restarted application in 1,462ms.
I/flutter ( 4998): loginSuccess? false isLoading? false
I/flutter ( 4998): loginSuccess? null isLoading? null
I/flutter ( 4998): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 4998): The following assertion was thrown building StreamBuilder<bool>(dirty, state:
I/flutter ( 4998): _StreamBuilderBaseState<bool, AsyncSnapshot<bool>>#34870):
I/flutter ( 4998): Failed assertion: boolean expression must not be null
I/flutter ( 4998):
I/flutter ( 4998): Either the assertion indicates an error in the framework itself, or we should provide substantially
I/flutter ( 4998): more information in this error message to help you determine and fix the underlying cause.
I/flutter ( 4998): In either case, please report this assertion by filing a bug on GitHub:
I/flutter ( 4998):   https://github.com/flutter/flutter/issues/new?template=BUG.md
I/flutter ( 4998):
I/flutter ( 4998): When the exception was thrown, this was the stack:
I/flutter ( 4998): #0      LoginPage.build.<anonymous closure>.<anonymous closure>

1 个答案:

答案 0 :(得分:0)

正如用户@user10539074 所提到的,您需要为流构建器的第一次运行设置初始数据 - 即。在您的代码中 - 如果 authBloc.isLoading 是 bool,则更改:

StreamBuilder(
          stream: authBloc.isLoading,
          builder: (context, snapshot2) {

到:

StreamBuilder(
          stream: authBloc.isLoading,
          initialData: true,
          builder: (context, snapshot2) {