#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.