简体   繁体   English

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

[英]How to add callback using BLoC pattern in flutter?

I am calling login api on button click, I am able to get response from server but on clicking on button it doesn't show progress bar. 我在单击按钮时调用了登录api,我能够从服务器获取响应,但是在单击按钮时它不显示进度栏。 I am using BLoC pattern for this. 我为此使用BLoC模式。 Here is the code, 这是代码,

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();
      }
    },
  );
 }
}

This is my login_screen.dart. 这是我的login_screen.dart。 And my bloc class for api call is: 我的api调用bloc类是:

postData() async {
LoginResponse response = await _repository.postData(_loginResource);
_subject.sink.add(response);

} }

I am able to parse json api, but not able to get the response of my model ie, 'LoginResponse' in login_screen.dart class and also the CircularProgressBar doesn't show when api is called on button click. 我能够解析json api,但是无法获取我的模型的响应,即login_screen.dart类中的“ LoginResponse”,并且当单击按钮时调用api时,CircularProgressBar也不会显示。

Code of the BLoC class is : 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();

Kindly let me know what I am missing. 请让我知道我在想什么。 Thanks in advance :) 提前致谢 :)

Well here we go. 好了,我们去。 I make some changes in your UI layer and in BLoC class with order to accomplish what you're asking for. 我在您的UI层和BLoC类中进行了一些更改,以完成您的要求。 I will firstly show the pieces of code that I insert and explain what I was think when I wrote it and after all I will paste the entire source code will all changes. 首先,我将展示我插入的代码片段,并解释我编写代码时的想法,毕竟我将粘贴整个源代码,以进行所有更改。 Maybe you can use the concept that I had used to adapt the source code to your needs. 也许您可以使用我用来使源代码适应您的需求的概念。 All code has comments so please read it will help you a lot. 所有代码都有注释,因此请阅读它将对您有很大帮助。

First of all I create an enum to represent the status of the login process and a class that holds the login process status and a message about it. 首先,我创建一个enum来表示登录过程的状态,并创建一个类来保存登录过程的状态和有关此消息的消息。 Both are part of your UI layer. 两者都是您的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});
}

In _LoginFormState class inside build method I inserted a StreamBuilder that will show and hide the progressbar when the login is happening or show an error widget. 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();
              }
            },
          ),

        ],
      ),
    );
  }

And the last change in your UI layer was in submitButton method the only change was in onPress event of your button now it calls bloc.loginSubmit method. UI层中的最后一个更改是submitButton方法,唯一的更改是按钮的onPress事件,现在它调用bloc.loginSubmit方法。

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

Now all the changes are in BLoC class. 现在所有更改都在BLoC类中。 Basically I created a new subject for handling the state changes of login process using LoginStatus enum and LoginState class and tell to view what widget must be showed to user. 基本上,我使用LoginStatus枚举和LoginState类创建了一个用于处理登录过程的状态更改的新主题,并告诉查看必须向用户显示哪些小部件。

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

All the login state changes handling I wrote inside postData method. 我在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);
  }

With this approach you avoid send to your UI layer objects from you model layer like LoginResponse class objects and this kind of concept makes your code more clean and do not broken MVC pattern and your UI layer holds only layout code. 使用这种方法,您可以避免将模型层的对象(例如LoginResponse类对象)发送到UI层,并且这种概念可以使您的代码更清晰,并且不会破坏MVC模式,并且UI层仅包含布局代码。

Make some tests, I didn't, adapt to your needs and comment if you need something I will answer when I can. 做一些测试,我没有,请适应您的需求,并在需要时发表评论,我会在可能时回答您。

The entire source code: 整个源代码:

/// 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();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM