How to add callback using BLoC pattern in flutter?

时间:2019-04-08 13:21:47

标签: 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. I am using BLoC pattern for this. 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. And my bloc class for api call is:

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 i.e, 'LoginResponse' in login_screen.dart class and also the CircularProgressBar doesn't show when api is called on button click.

Code of the BLoC class is :

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 :)

1 个答案:

答案 0 :(得分:2)

好了,我们走了。我在您的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);
  }

通过这种方法,您可以避免将模型层中的对象(例如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();