如何在颤振中使用 BLoC 模式添加回调?

Ann*_*nnu 4 flutter

我在单击按钮时调用登录 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();
      }
    },
  );
 }
}
Run Code Online (Sandbox Code Playgroud)

这是我的 login_screen.dart。我用于 api 调用的 bloc 类是:

postData() async {
LoginResponse response = await _repository.postData(_loginResource);
_subject.sink.add(response);
Run Code Online (Sandbox Code Playgroud)

}

我能够解析 json api,但无法获得我的模型的响应,即 login_screen.dart 类中的“LoginResponse”,并且 CircularProgressBar 在单击按钮时调用 api 时不显示。

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();
Run Code Online (Sandbox Code Playgroud)

请让我知道我缺少什么。提前致谢 :)

Mar*_*ura 11

好吧,我们走了。我在您的 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});
}
Run Code Online (Sandbox Code Playgroud)

在 _LoginFormState 类中的build方法中,我插入了一个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();
              }
            },
          ),

        ],
      ),
    );
  }
Run Code Online (Sandbox Code Playgroud)

UI 层中的最后一个更改是在submitButton方法中,唯一的更改是在onPress您的按钮事件中,现在它调用了bloc.loginSubmit方法。

return RaisedButton(
          onPressed:() => bloc.loginSubmit(), // the only change
          child: const Text('Login'),
          textColor: Colors.white,
          color: Colors.blueAccent,
        );
Run Code Online (Sandbox Code Playgroud)

现在所有的变化都在 BLoC 类中。基本上,我创建了一个新主题,用于使用LoginStatusenum 和LoginStateclass处理登录过程的状态更改,并告诉查看必须向用户显示的小部件。

//The subject and a get method to expose his stream
final PublishSubject<LoginState> _loginStateSubject = new PublishSubject();
Observable<LoginState> get loginStateStream => _loginStateSubject.stream;
Run Code Online (Sandbox Code Playgroud)

我在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);
  }
Run Code Online (Sandbox Code Playgroud)

使用这种方法,您可以避免像LoginResponse类对象一样从模型层发送到 UI 层对象,这种概念使您的代码更干净,不会破坏 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();
Run Code Online (Sandbox Code Playgroud)