我只是在尝试抖动,我似乎无法使组件根据我的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>
答案 0 :(得分:0)
正如用户@user10539074 所提到的,您需要为流构建器的第一次运行设置初始数据 - 即。在您的代码中 - 如果 authBloc.isLoading 是 bool,则更改:
StreamBuilder(
stream: authBloc.isLoading,
builder: (context, snapshot2) {
到:
StreamBuilder(
stream: authBloc.isLoading,
initialData: true,
builder: (context, snapshot2) {