How to do backend validation using BLOC pattern in Flutter TextField? How to do backend validation using BLOC pattern in Flutter TextField? dart dart

How to do backend validation using BLOC pattern in Flutter TextField?


You are probably not longer looking for a solution but based on the upvotes of the question I wanted to provide an answer nonetheless.

I'm not sure if I understand your code correctly and it looks like you are implementing BLoC yourself, so this is quite a disclaimer because I am providing a solution which uses the BLoC implementation by Felix Angelov (pub.dev/packages/bloc).

The outcome of the code described below

Implementation Result

Code and Approach:

First I created an empty project, and added the BLoC Library; In pubspec.yaml i added

flutter_bloc: ^3.2.0

Then i created a new bloc BackendValidationBloc with one event ValidateInput and multiple states as shown in the following code snippets.

Event Code:

Most of the time I start by defining the event which is quite simple in my example:

part of 'backend_validation_bloc.dart';@immutableabstract class BackendValidationEvent {}class ValidateInput extends BackendValidationEvent {  final String input;  ValidateInput({@required this.input});}

State Code:

Then you probably want one state with multiple properties or multiple states. I decided to go with one state with multiple properties because in my opinion it is easier to handle in the UI. In this example I recommend giving feedback to the user because validating the input via a backend might take some time. Therefore the BackendValidationState features two states: loading and validated.

part of 'backend_validation_bloc.dart';@immutableclass 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 Code:

At last, you "connect" event with states by defining the bloc which makes calls to your backend:

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 Code:

In case you are not familiar with how the implemented BLoC is used in the UI, this is the frontend code using the state to feed different values (for the actual error message and for user feedback while waiting for the backend response) to the errorText property of the 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),            );          },        ));  }}

Connecting a real backend

So I kinda faked a backend, but if you want to use a real one it is common to implement a Repository and pass it to the BLoC in a constructor, which makes using different implementations of backend easier (if properly implemented against interfaces). If you want a more detailed tutorial check out Felix Angelov's tutorials (they are pretty good)

Hope this helps you or others.