如何在Flutter TextField中使用BLOC模式进行后端验证?

时间:2018-12-26 07:34:45

标签: dart flutter rxdart bloc

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

如何使用带有TextField小部件的BLOC模式进行异步验证? 我应该使用StreamTransformerStream添加错误吗?我尝试使用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,
            ),
          );
        },
      )

1 个答案:

答案 0 :(得分:3)

您可能不再在寻找解决方案,但是基于问题的支持,我仍然想提供答案。

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

以下代码的结果

Implementation Result

代码和方法:

首先,我创建了一个空项目,并添加了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);
  }
}

集团代码:

最后,通过定义对后端进行调用的块,将事件与状态“连接”起来:

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(它们非常不错)

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