Как мне управлять условным рендерингом во Flutter, используя Bloc, без получения ошибки «логическое выражение не должно быть пустым»? - PullRequest
0 голосов
/ 02 мая 2019

Я просто пробую флаттер, и я не могу заставить компоненты отображаться условно на основе BehaviourStream в моем блоке.

Я хочу сначала показать виджет "_buildPage ()" (который является формой авторизации), затем, когда _isLoading имеет значение true, но (_loginSucceded имеет значение false), я хочу показать счетчик. И наконец, когда _loginSucceded имеет значение true, а _isLoading имеет значение false, я хочу перенаправить пользователя.

Фактическое поведение - после отправки формы загрузчик показывает, как ожидалось. После того, как ответ успешно получен с сервера, аутентификация для снова отображается.

Я думаю, что моя логика в порядке, но кажется, что когда я устанавливаю значения потока в конструкторе Bloc, что-то еще вызывает перерисовку приложения, что приводит к нулевым значениям в потоке.

Есть ли способ убедиться, что поток всегда имеет базовые значения после того, как конструктор запустился после инициализации?

Или есть лучший способ управлять этим сценарием? Я только начал смотреть на Флаттера с JS-фона, поэтому я вполне могу что-то упустить.

Код блока:

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>
...