【问题标题】:How to do backend validation using BLOC pattern in Flutter TextField?如何在 Flutter TextField 中使用 BLOC 模式进行后端验证?
【发布时间】:2019-05-24 12:44:09
【问题描述】:

我想创建一个 TextField 来检查数据库中是否存在该值。

如何使用带有TextField 小部件的BLOC 模式进行异步验证? 我应该使用StreamTransformer 将错误添加到Stream 吗?我尝试使用DebounceStreamTransformer,但它只是阻止Stream 接收新值。

这是我的Observable

 Observable<String> get valueStream => valueController.stream.transform(PropertyNameExist.handle('Blabla', null));

这是我的StreamTransformer

class PropertyNameExist implements StreamTransformerValidator {
  static StreamTransformer<String, String> handle(String fieldname, String data) {
    Http http = new Http();
    return StreamTransformer<String, String>.fromHandlers(
        handleData: (String stringData, sink) {
          http.post('/my_api',data:{
            'property_name':stringData,
          }).then((Response response){
            Map<String,dynamic> responseData = jsonDecode(response.data);
            bool isValid = responseData['valid'] == 'true';
            if(isValid){
              sink.add(stringData);
            } else {
              sink.addError('Opps Error');
            }
          });
    });
  }
}

这是我的Widget

StreamBuilder<String>(
        stream: valueStream,
        builder: (context, AsyncSnapshot<String> snapshot) {
          if (snapshot.hasData) {
            _textInputController.setTextAndPosition(snapshot.data);
          }
          return TextField(
            controller: _textInputController,
            onChanged: (String newVal) {
              updateValue(newVal);
            },
            decoration: InputDecoration(
              errorText: snapshot.error,
            ),
          );
        },
      )

【问题讨论】:

  • github.com/shiang/flutter-form-with-validation-BLOC 签出这个存储库,这可能会有所帮助
  • @dlohani 该存储库没有后端/异步验证示例
  • 仅供参考,在验证器文件中,创建自己的验证器,使用http包从后端验证,
  • @dlohani 这正是我对我提供的代码所做的。问题是,StreamTransformer 在验证服务器时阻止 Stream 接收新值
  • @firmansyahramadhan 你能解决你的问题吗?

标签: dart flutter rxdart bloc


【解决方案1】:

您可能不再寻找解决方案,而是基于我想提供答案的问题的赞成票。

我不确定我是否正确理解了您的代码,并且看起来您正在自己实现 BLoC,所以这是一个免责声明,因为我提供的解决方案使用 Felix Angelov (pub.dev/packages/bloc) 的 BLoC 实现。

下面描述的代码的结果

代码和方法:

首先我创建了一个空项目,并添加了 BLoC 库;在pubspec.yaml我添加了

flutter_bloc: ^3.2.0

然后我创建了一个新的 bloc BackendValidationBloc,其中包含一个事件 ValidateInput 和多个状态,如以下代码 sn-ps 所示。

事件代码:

大多数时候,我从定义事件开始,这在我的示例中非常简单:

part of 'backend_validation_bloc.dart';

@immutable
abstract class BackendValidationEvent {}

class ValidateInput extends BackendValidationEvent {
  final String input;

  ValidateInput({@required this.input});
}

州代码:

那么您可能想要一个具有多个属性或多个状态的状态。我决定使用具有多个属性的一种状态,因为在我看来它更容易在 UI 中处理。在此示例中,我建议向用户提供反馈,因为通过后端验证输入可能需要一些时间。因此BackendValidationState 具有两种状态:loadingvalidated

part of 'backend_validation_bloc.dart';

@immutable
class BackendValidationState {
  final bool isInProcess;
  final bool isValidated;
  bool get isError => errorMessage.isNotEmpty;
  final String errorMessage;

  BackendValidationState(
      {this.isInProcess, this.isValidated, this.errorMessage});

  factory BackendValidationState.empty() {
    return BackendValidationState(
        isInProcess: false, isValidated: false);
  }

  BackendValidationState copyWith(
      {bool isInProcess, bool isValidated, String errorMessage}) {
    return BackendValidationState(
      isValidated: isValidated ?? this.isValidated,
      isInProcess: isInProcess ?? this.isInProcess,
      // This is intentionally not defined as
      // errorMessage: errorMessage ?? this.errorMessage
      // because if the errorMessage is null, it means the input was valid
      errorMessage: errorMessage,
    );
  }

  BackendValidationState loading() {
    return this.copyWith(isInProcess: true);
  }

  BackendValidationState validated({@required String errorMessage}) {
    return this.copyWith(errorMessage: errorMessage, isInProcess: false);
  }
}

区号:

最后,您通过定义调用后端的 bloc 将事件与状态“连接”:

import 'dart:async';

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:meta/meta.dart';

part 'backend_validation_event.dart';
part 'backend_validation_state.dart';

class BackendValidationBloc
    extends Bloc<BackendValidationEvent, BackendValidationState> {
  @override
  BackendValidationState get initialState => BackendValidationState.empty();

  @override
  Stream<BackendValidationState> mapEventToState(
    BackendValidationEvent event,
  ) async* {
    if (event is ValidateInput) {
      yield this.state.loading();
      String backendValidationMessage =
          await this.simulatedBackendFunctionality(event.input);
      yield this.state.validated(errorMessage: backendValidationMessage);
    }
  }

  Future<String> simulatedBackendFunctionality(String input) async {
    // This simulates delay of the backend call
    await Future.delayed(Duration(milliseconds: 500));
    // This simulates the return of the backend call
    String backendValidationMessage;
    if (input != 'hello') {
      backendValidationMessage = "Input does not equal to 'hello'";
    }
    return backendValidationMessage;
  }
}

用户界面代码:

如果您不熟悉如何在 UI 中使用已实现的 BLoC,这是使用状态来提供不同值的前端代码(用于实际错误消息和等待后端响应时的用户反馈) TextField 的 errorText 属性。

import 'package:backend_validation_using_bloc/bloc/backend_validation_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() => runApp(App());

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: BlocProvider<BackendValidationBloc>(
          create: (context) => BackendValidationBloc(), child: HomeScreen()),
    );
  }
}

class HomeScreen extends StatefulWidget {
  HomeScreen({Key key}) : super(key: key);

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  TextEditingController textEditingController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(),
        body: BlocBuilder<BackendValidationBloc, BackendValidationState>(
          builder: (BuildContext context, BackendValidationState state) {
            return TextField(
              controller: textEditingController,
              onChanged: (String currentValue) {
                BlocProvider.of<BackendValidationBloc>(context)
                    .add(ValidateInput(input: currentValue));
              },
              decoration: InputDecoration(errorText: state.isInProcess ? 'Valiating input...' : state.errorMessage),
            );
          },
        ));
  }
}

连接真实后端

所以我有点伪造了一个后端,但是如果你想使用一个真实的后端,通常会在构造函数中实现 Repository 并将其传递给 BLoC,这使得使用不同的后端实现更容易(如果针对接口正确实施)。如果您想要更详细的教程,请查看Felix Angelov's tutorials(它们非常好)

希望这对您或其他人有所帮助。

【讨论】:

    猜你喜欢
    • 2020-02-21
    • 2019-11-20
    • 1970-01-01
    • 2019-06-13
    • 2019-02-07
    • 2021-05-28
    • 2020-11-07
    • 2017-10-26
    • 1970-01-01
    相关资源
    最近更新 更多