- r - 以节省内存的方式增长 data.frame
- ruby-on-rails - ruby/ruby on rails 内存泄漏检测
- android - 无法解析导入android.support.v7.app
- UNIX 域套接字与共享内存(映射文件)
我在单击按钮时调用登录 api,我能够从服务器获得响应,但在单击按钮时它不显示进度条。我为此使用 BLoC 模式。这是代码,
import 'package:flutter/material.dart';
import '../blocs/bloc.dart';
import '../blocs/provider.dart';
import '../models/login_response.dart';
class LoginScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Provider(
child: new Scaffold(
body: Container(
child: LoginForm(),
),
),
);
}
}
class LoginForm extends StatefulWidget {
// since its a stateful widget we need to create state for it.
const LoginForm({Key key}) : super(key: key);
@override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
@override
Widget build(BuildContext context) {
return Form(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 50),
),
// Start creating widget here.
emailField(),
passwordField(),
Container(margin: EdgeInsets.only(top: 25.0)),
submitButton()
],
),
);
}
Widget emailField() {
return StreamBuilder(
stream: bloc.email,
builder: (context, snapshot) {
return TextField(
onChanged: bloc.changeEmail,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: 'you@example.com',
labelText: 'Email Address',
errorText: snapshot.error
),
);
}
);
}
Widget passwordField() {
return StreamBuilder(
stream: bloc.password,
builder: (context, snapshot) {
return TextField(
onChanged: bloc.changePassword,
obscureText: true,
decoration: InputDecoration(
labelText: 'Please enter your password',
hintText: 'Password',
errorText: snapshot.error
),
);
},
);
}
Widget submitButton() {
return StreamBuilder(
stream: bloc.submitValid,
builder: (context, snapshot) {
return RaisedButton(
onPressed:() => showWidgetForNetworkCall(context),
// onPressed: () {
// // Do submit button action.
// showWidgetForNetworkCall(context);
// // callLoginApi();
// },
child: const Text('Login'),
textColor: Colors.white,
color: Colors.blueAccent,
);
},
);
}
// Loading Widget
Widget _buildLoadingWidget() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Loading data from API...", textDirection: TextDirection.ltr), CircularProgressIndicator()
],
),
);
}
// // Error Widget
Widget _buildErrorWidget(String error) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Loading error data from API...", textDirection: TextDirection.ltr), CircularProgressIndicator()
],
),
);
}
// show server data
showServerData() {
print(" Servr >>>>>> Data : ");
}
Widget showWidgetForNetworkCall(BuildContext context) {
bloc.loginSubmit();
return StreamBuilder(
stream: bloc.loginSubject.stream,
builder: (context, AsyncSnapshot<LoginResponse>snapshot){
if (snapshot.hasData) {
return showServerData();
} else if (snapshot.hasError) {
return _buildErrorWidget(snapshot.error);
} else {
return _buildLoadingWidget();
}
},
);
}
}
这是我的 login_screen.dart。我用于 api 调用的 bloc 类是:
postData() async {
LoginResponse response = await _repository.postData(_loginResource);
_subject.sink.add(response);
我能够解析 json api,但无法获得我的模型的响应,即 login_screen.dart 类中的“LoginResponse”,并且在单击按钮时调用 api 时 CircularProgressBar 也不会显示。
BLoC 类的代码是:
import 'dart:async';
import 'package:rxdart/rxdart.dart';
import 'validators.dart';
import '../models/login_response.dart';
import '../repository/login_repository.dart';
import '../resources/login_resource.dart';
class Bloc extends Object with Validators {
final LoginRepository _repository = LoginRepository();
final BehaviorSubject<LoginResponse> _subject =
BehaviorSubject<LoginResponse>();
LoginResource _loginResource = LoginResource();
final _email = BehaviorSubject<String>(); // Declaring variable as private
final _password = BehaviorSubject<String>(); // Declaring variable as private
// Add data to stream (Its like setter)
Stream<String> get email => _email.stream.transform(validateEmail);
Stream<String> get password =>
_password.stream.transform(validatePassword);
Stream<bool> get submitValid => Observable.combineLatest2(email, password, (e, p) => true);
// Change data. For retrieveing email value.
Function(String) get changeEmail => _email.sink.add;
Function(String) get changePassword => _password.sink.add;
loginSubmit() {
_loginResource.email = "bar1";
_loginResource.password = "bar2";
postData();
}
postData() async {
LoginResponse response = await _repository.postData(_loginResource);
_subject.sink.add(response);
}
dispose() {
_email.close();
_password.close();
_subject.close();
}
BehaviorSubject<LoginResponse> get loginSubject => _subject;
}
final bloc = Bloc();
请让我知道我缺少什么。提前致谢:)
最佳答案
好了,我们开始吧。我在您的 UI 层和 BLoC 类中进行了一些更改,以完成您的要求。我将首先展示我插入的代码片段并解释我在编写代码时的想法,最后我将粘贴整个源代码将所有更改。也许您可以使用我曾经使用过的概念来根据您的需要调整源代码。所有代码都有注释,请仔细阅读,对您有很大帮助。
首先,我创建了一个enum
来表示登录过程的状态,以及一个保存登录过程状态和相关消息的类。两者都是您的 UI 层的一部分。
/// NON_LOGIN: means that login is not happening
/// LOGGIN: means that login is happening
/// LOGIN_ERROR: means that something is wrong with login
/// LOGIN_SUCCESS: the login process was a success.
enum LoginStatus { NON_LOGIN, LOGGING, LOGIN_SUCCESS, LOGIN_ERROR }
class LoginState {
final LoginStatus status;
final String message;
LoginState({this.status, this.message});
}
在 build
方法内的 _LoginFormState 类中,我插入了一个 StreamBuilder
,它将在登录发生时显示和隐藏进度条或显示错误小部件。
@override
Widget build(BuildContext context) {
return Form(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 50),
),
// Start creating widget here.
emailField(),
passwordField(),
Container(margin: EdgeInsets.only(top: 25.0)),
submitButton(),
StreamBuilder<LoginState>(
stream: bloc.loginStateStream,
builder: (context, AsyncSnapshot<LoginState> snapshot){
if ( !snapshot.hasData )
return Container();
switch(snapshot.data.status){
case LoginStatus.LOGGING:
return _buildLoadingWidget();
case LoginStatus.LOGIN_ERROR:
return _buildErrorWidget(snapshot.data.message);
case LoginStatus.LOGIN_SUCCESS:
// Here you can go to another screen after login success.
return Center(child: Text("${snapshot.data.message}"),);
case LoginStatus.NON_LOGIN:
default:
return Container();
}
},
),
],
),
);
}
UI 层中的最后一个更改是在 submitButton
方法中,唯一的更改是在按钮的 onPress
事件中,现在它调用 bloc.loginSubmit
方法。
return RaisedButton(
onPressed:() => bloc.loginSubmit(), // the only change
child: const Text('Login'),
textColor: Colors.white,
color: Colors.blueAccent,
);
现在所有更改都在 BLoC 类中。基本上,我使用 LoginStatus
枚举和 LoginState
类创建了一个新主题来处理登录过程的状态变化,并告诉我必须向用户显示哪些小部件。
//The subject and a get method to expose his stream
final PublishSubject<LoginState> _loginStateSubject = new PublishSubject();
Observable<LoginState> get loginStateStream => _loginStateSubject.stream;
我在 postData
方法中编写的所有登录状态更改处理。
postData() async {
// this call will change the UI and a CircularProgressBar will be showed.
changeLoginState(state: LoginState( status: LoginStatus.LOGGING, message: "logging") );
// waiting for login response!
LoginResponse response = await _repository.postData(_loginResource);
print(response); // just to text debug your response.
//Here you can verify if the login process was successfully or if there is
// some kind of error based in your LoginResponse model class.
// avoiding write this logic in UI layer.
if(response.hasError){
changeLoginState(state: LoginState(status: LoginStatus.LOGIN_ERROR,
message: response.errorMessage)
);
// and after 1.5 seconds we make the error message disappear from UI.
// you can do this in UI layer too
Future.delayed(Duration(milliseconds: 1500), (){
// you can pass null to state property, will make the same effect
changeLoginState(state: LoginState(status: LoginStatus.NON_LOGIN)); });
}
else {
changeLoginState(state: LoginState(status:
LoginStatus.LOGIN_SUCCESS, message: "Login Success"));
}
//_subject.sink.add(response);
}
使用这种方法,您可以避免从您的模型层向您的 UI 层发送对象,例如 LoginResponse
类对象,这种概念使您的代码更干净,不会破坏 MVC 模式,并且您的 UI 层保持只有布局代码。
做一些测试,我没有,适应你的需求,如果你需要什么,我会在我能回答的时候发表评论。
完整源码:
/// NON_LOGIN: means that login is not happening
/// LOGGIN: means that login is happening
/// LOGIN_ERROR: means that something is wrong with login
/// LOGIN_SUCCESS: the login process was a success.
///
enum LoginStatus { NON_LOGIN, LOGGING, LOGIN_SUCCESS, LOGIN_ERROR }
class LoginState {
final LoginStatus status;
final String message;
LoginState({this.status, this.message});
}
class LoginScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Provider(
child: new Scaffold(
body: Container(
child: LoginForm(),
),
),
);
}
}
class LoginForm extends StatefulWidget {
// since its a stateful widget we need to create state for it.
const LoginForm({Key key}) : super(key: key);
@override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
@override
Widget build(BuildContext context) {
return Form(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 50),
),
// Start creating widget here.
emailField(),
passwordField(),
Container(margin: EdgeInsets.only(top: 25.0)),
submitButton(),
StreamBuilder<LoginState>(
stream: bloc.loginStateStream,
builder: (context, AsyncSnapshot<LoginState> snapshot){
if ( !snapshot.hasData )
return Container();
switch(snapshot.data.status){
case LoginStatus.LOGGING:
return _buildLoadingWidget();
case LoginStatus.LOGIN_ERROR:
return _buildErrorWidget(snapshot.data.message);
case LoginStatus.LOGIN_SUCCESS:
// Here you can go to another screen after login success.
return Center(child: Text("${snapshot.data.message}"),);
case LoginStatus.NON_LOGIN:
default:
return Container();
}
},
),
],
),
);
}
Widget emailField() {
return StreamBuilder(
stream: bloc.email,
builder: (context, snapshot) {
return TextField(
onChanged: bloc.changeEmail,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: 'you@example.com',
labelText: 'Email Address',
errorText: snapshot.error
),
);
}
);
}
Widget passwordField() {
return StreamBuilder(
stream: bloc.password,
builder: (context, snapshot) {
return TextField(
onChanged: bloc.changePassword,
obscureText: true,
decoration: InputDecoration(
labelText: 'Please enter your password',
hintText: 'Password',
errorText: snapshot.error
),
);
},
);
}
Widget submitButton() {
return StreamBuilder(
stream: bloc.submitValid,
builder: (context, snapshot) {
return RaisedButton(
onPressed:() => bloc.loginSubmit(),
child: const Text('Login'),
textColor: Colors.white,
color: Colors.blueAccent,
);
},
);
}
// Loading Widget
Widget _buildLoadingWidget() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Loading data from API...", textDirection: TextDirection.ltr), CircularProgressIndicator()
],
),
);
}
// // Error Widget
Widget _buildErrorWidget(String error) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Loading error data from API...", textDirection: TextDirection.ltr), CircularProgressIndicator()
],
),
);
}
/*
// show server data
showServerData() {
print(" Servr >>>>>> Data : ");
}
Widget showWidgetForNetworkCall() {
return StreamBuilder(
stream: bloc.loginSubject.stream,
builder: (context, AsyncSnapshot<LoginResponse>snapshot){
if (snapshot.hasData) {
return showServerData();
} else if (snapshot.hasError) {
return _buildErrorWidget(snapshot.error);
} else {
return _buildLoadingWidget();
}
},
);
}*/
}
class Bloc extends Object with Validators {
//final BehaviorSubject<LoginResponse> _subject = BehaviorSubject<LoginResponse>();
//BehaviorSubject<LoginResponse> get loginSubject => _subject;
final LoginRepository _repository = LoginRepository();
final PublishSubject<LoginState> _loginStateSubject = new PublishSubject();
Observable<LoginState> get loginStateStream => _loginStateSubject.stream;
LoginResource _loginResource = LoginResource();
final _email = BehaviorSubject<String>(); // Declaring variable as private
final _password = BehaviorSubject<String>(); // Declaring variable as private
// Add data to stream (Its like setter)
Stream<String> get email => _email.stream.transform(validateEmail);
Stream<String> get password => _password.stream.transform(validatePassword);
Stream<bool> get submitValid => Observable.combineLatest2(email, password, (e, p) => true);
// Change data. For retrieveing email value.
Function(String) get changeEmail => _email.sink.add;
Function(String) get changePassword => _password.sink.add;
void changeLoginState({LoginState state } ) => _loginStateSubject.sink.add(state);
loginSubmit() {
_loginResource.email = "bar1";
_loginResource.password = "bar2";
postData();
}
postData() async {
// this call will change the UI and a CircularProgressBar will be showed.
changeLoginState(state: LoginState( status: LoginStatus.LOGGING, message: "logging") );
// waiting for login response!
LoginResponse response = await _repository.postData(_loginResource);
print(response); // just to text debug your response.
//Here you can verify if the login process was successfully or if there is
// some kind of error based in your LoginResponse model class.
if(response.hasError){
changeLoginState(state: LoginState(status: LoginStatus.LOGIN_ERROR,
message: response.errorMessage)
);
// and after 1.5 seconds we make the error message disappear from UI.
// you can do this in UI layer too
Future.delayed(Duration(milliseconds: 1500), (){
// you can pass null to state property, will make the same effect
changeLoginState(state: LoginState(status: LoginStatus.NON_LOGIN)); });
}
else {
changeLoginState(state: LoginState(status:
LoginStatus.LOGIN_SUCCESS, message: "Login Success"));
}
//_subject.sink.add(response);
}
dispose() {
_loginStateSubject.close();
_email.close();
_password.close();
//_subject.close();
}
}
final bloc = Bloc();
关于flutter - 如何在 flutter 中使用 BLoC 模式添加回调?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55574485/
我是一名优秀的程序员,十分优秀!