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