Update TextFormField's error outside of the validator
You can use flutter_form_bloc.
Each field have the method addError
and you can call anywhere, in your case it would be in the onSubmitting
method after receiving the response from the server.
class MyFormBloc extends FormBloc<String, String> { final email = TextFieldBloc(); MyFormBloc() { addFieldBlocs(fieldBlocs: [email]); } @override void onSubmitting() async { // Awesome logic... username.addError('That email is taken. Try another.'); }}
You can also have asynchronous validators with debounce time
class MyFormBloc extends FormBloc<String, String> { final username = TextFieldBloc( asyncValidatorDebounceTime: Duration(milliseconds: 300), ); MyFormBloc() { addFieldBlocs(fieldBlocs: [username]); username.addAsyncValidators([_checkUsername]); } Future<String> _checkUsername(String username) async { await Future.delayed(Duration(milliseconds: 500)); if (username.toLowerCase() != 'flutter dev') { return 'That username is already taken'; } return null; }}
Here is a small demo that you can run and the tutorial is in the form bloc website
pubspec.yaml
dependencies: flutter_form_bloc: ^0.11.0
main.dart
import 'package:flutter/material.dart';import 'package:flutter_form_bloc/flutter_form_bloc.dart';void main() => runApp(App());class App extends StatelessWidget { const App({Key key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: SubmissionErrorToFieldForm(), ); }}class SubmissionErrorToFieldFormBloc extends FormBloc<String, String> { final username = TextFieldBloc(); SubmissionErrorToFieldFormBloc() { addFieldBlocs( fieldBlocs: [ username, ], ); } @override void onSubmitting() async { print(username.value); await Future<void>.delayed(Duration(milliseconds: 500)); if (username.value.toLowerCase() == 'dev') { username.addError( 'Cached - That username is taken. Try another.', isPermanent: true, ); emitFailure(failureResponse: 'Cached error was added to username field.'); } else { username.addError('That username is taken. Try another.'); emitFailure(failureResponse: 'Error was added to username field.'); } }}class SubmissionErrorToFieldForm extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( create: (context) => SubmissionErrorToFieldFormBloc(), child: Builder( builder: (context) { final formBloc = BlocProvider.of<SubmissionErrorToFieldFormBloc>(context); return Theme( data: Theme.of(context).copyWith( inputDecorationTheme: InputDecorationTheme( border: OutlineInputBorder( borderRadius: BorderRadius.circular(20), ), ), ), child: Scaffold( appBar: AppBar(title: Text('Submission Error to Field')), body: FormBlocListener<SubmissionErrorToFieldFormBloc, String, String>( onSubmitting: (context, state) { LoadingDialog.show(context); }, onSuccess: (context, state) { LoadingDialog.hide(context); Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => SuccessScreen())); }, onFailure: (context, state) { LoadingDialog.hide(context); Scaffold.of(context).showSnackBar( SnackBar(content: Text(state.failureResponse))); }, child: SingleChildScrollView( physics: ClampingScrollPhysics(), child: Padding( padding: const EdgeInsets.all(8.0), child: Column( children: <Widget>[ TextFieldBlocBuilder( textFieldBloc: formBloc.username, keyboardType: TextInputType.multiline, decoration: InputDecoration( labelText: 'Username', prefixIcon: Icon(Icons.sentiment_very_satisfied), ), ), Padding( padding: const EdgeInsets.all(8.0), child: Text('"dev" will add a cached error'), ), RaisedButton( onPressed: formBloc.submit, child: Text('SUBMIT'), ), ], ), ), ), ), ), ); }, ), ); }}class LoadingDialog extends StatelessWidget { static void show(BuildContext context, {Key key}) => showDialog<void>( context: context, useRootNavigator: false, barrierDismissible: false, builder: (_) => LoadingDialog(key: key), ).then((_) => FocusScope.of(context).requestFocus(FocusNode())); static void hide(BuildContext context) => Navigator.pop(context); LoadingDialog({Key key}) : super(key: key); @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async => false, child: Center( child: Card( child: Container( width: 80, height: 80, padding: EdgeInsets.all(12.0), child: CircularProgressIndicator(), ), ), ), ); }}class SuccessScreen extends StatelessWidget { SuccessScreen({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Icon(Icons.tag_faces, size: 100), SizedBox(height: 10), Text( 'Success', style: TextStyle(fontSize: 54, color: Colors.black), textAlign: TextAlign.center, ), SizedBox(height: 10), RaisedButton.icon( onPressed: () => Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) => SubmissionErrorToFieldForm())), icon: Icon(Icons.replay), label: Text('AGAIN'), ), ], ), ), ); }}
Why don't you add server call inside a validator function.Use a validator inside a TextFormField as given below,
TextFormField( validator: _validateEmail, onSaved: (String value) { email = value; }, ), String _validateEmail(String value) async {//call to a server inside a validator function dynamic response = await someServerCall(); String _token=""; if (response.token) { // Valid, use token setState((){ _token = response.token }); return null; } else { // INVALID, update error text somehow return "error"; } }
if validator gets null then it will not show any error, but if it gets any string then it will display that string as an error.Now for the button
RaisedButtton( child: Text('SUBMIT'), onPressed: (){ if (_formKey.currentState.validate()){} _formKey.currentState.save();})