Как добавить обратный вызов, используя шаблон блока в flutter?

#flutter

#flutter

Вопрос:

Я вызываю login api при нажатии кнопки, я могу получить ответ от сервера, но при нажатии на кнопку он не показывает индикатор выполнения. Для этого я использую шаблон блока. Вот код,

 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:

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

}

Я могу проанализировать json api, но не могу получить ответ моей модели, то есть «LoginResponse» в login_screen.класс dart, а также CircularProgressBar не отображаются при вызове api при нажатии кнопки.

Код класса блока :

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

Пожалуйста, дайте мне знать, чего мне не хватает. Заранее спасибо 🙂

Комментарии:

1. Можете ли вы поделиться своим кодом блока? Ну, о том, что ваша панель прогресса не отображается, потому что вы не помещаете ее в свое дерево виджетов. Если вы поделитесь своим кодом блока, возможно, я смогу реализовать какое-то решение.

2. @MarcosBoaventura, не могли бы вы помочь мне с фрагментом кода, как я могу показать панель прогресса при нажатии кнопки.

3. @MarcosBoaventura, я добавляю код моего блочного класса для вашей справки. Заранее спасибо 🙂

Ответ №1:

Ну, вот и все. Я вношу некоторые изменения в ваш уровень пользовательского интерфейса и в класс блоков, чтобы выполнить то, о чем вы просите. Сначала я покажу фрагменты кода, которые я вставляю, и объясню, что я думал, когда писал это, и после всего, что я вставлю, весь исходный код будет изменен. Может быть, вы можете использовать концепцию, которую я использовал, чтобы адаптировать исходный код к вашим потребностям. Весь код имеет комментарии, поэтому, пожалуйста, прочитайте, это вам очень поможет.

Прежде всего, я создаю enum для представления статуса процесса входа в систему и класса, который содержит статус процесса входа в систему и сообщение об этом. Оба являются частью вашего пользовательского интерфейса.

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

В _LoginFormState класс внутри build метода я вставил a 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();
              }
            },
          ),

        ],
      ),
    );
  }
  

И последнее изменение в вашем пользовательском интерфейсе было в submitButton методе, единственное изменение было в onPress случае, если ваша кнопка теперь вызывает bloc.loginSubmit метод.

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

Теперь все изменения внесены в класс блока. По сути, я создал новую тему для обработки изменений состояния процесса входа в систему с использованием LoginStatus enum и LoginState class и указываю, какой виджет должен быть показан пользователю.

 //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 объектам класса, и такая концепция делает ваш код более чистым и не нарушает шаблон MVC, а ваш слой пользовательского интерфейса содержит только код макета.

Сделайте несколько тестов, я этого не делал, адаптируйтесь к вашим потребностям и прокомментируйте, если вам что-то понадобится, я отвечу, когда смогу.

Весь исходный код:

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

Комментарии:

1. Привет @MarcosBoaventura, большое спасибо за помощь мне в этом! В настоящее время я изучаю flutter. Большое спасибо за помощь с шаблоном кода также 🙂

2. @Annu Если Марко ответил на ваш вопрос, вы должны признать его ответ правильным, нажав на V-образный значок под количеством голосов.

3. Хороший ответ с простым примером. Я только что заменил класс Observable на Stream в классе Bloc.