首页 > 解决方案 > 如何在 Flutter TextField 中使用 BLOC 模式进行后端验证?

问题描述

我想创建一个 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,
            ),
          );
        },
      )

标签: dartflutterrxdartbloc

解决方案


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

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

下面描述的代码的结果

实施结果

代码和方法:

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

flutter_bloc: ^3.2.0

然后我创建了一个BackendValidationBloc包含一个事件ValidateInput和多个状态的新块,如以下代码片段所示。

事件代码:

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

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,这是前端代码使用状态将不同的值(用于实际错误消息和等待后端响应时的用户反馈)提供给 errorText 属性的TextField

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

连接真实的后端

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

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


推荐阅读