Как во Flutter выводить текст валидатора TextFormField вне формы (то есть под кнопкой отправки)?
Я написал образец кода, который отображает сообщение, если пользователь нажимает кнопку входа в систему, когда внутри TextFormField нет строки. Он отобразит сообщение под TextFormField, предупреждающее пользователя ввести электронное письмо. Что я хочу сделать, так это отобразить это сообщение где-нибудь еще, возможно, под кнопкой входа в систему.
В идеале у меня есть текстовое поле для входа в систему и пароль, и вместо отображения сообщения под каждым текстовым полем я хочу отображать сообщение под кнопкой входа в систему. Любая помощь будет оценена по достоинству! Я изучил текстовые контроллеры, но не знаю, как реализовать, чтобы, если валидатор проходит без сообщения, он сохраняет виджеты на той же высоте, но только увеличивает высоту, когда валидатор не отображает сообщение.
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: TextFieldExample(),
),
),
);
}
}
class TextFieldExample extends StatefulWidget {
@override
_TextFieldExampleState createState() => _TextFieldExampleState();
}
class _TextFieldExampleState extends State<TextFieldExample> {
final _formKey = GlobalKey<FormState>();
String email = '';
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(vertical: 5),
padding: EdgeInsets.symmetric(horizontal: 15),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(29)),
child: Form(
key: _formKey,
child: Column(children: <Widget>[
TextFormField(
validator: (value) => value.isEmpty ? 'Enter an email' : null,
onChanged: (value) {
setState(() => email = value);
},
decoration:
InputDecoration(icon: Icon(Icons.person), hintText: "Email"),
),
Container(
margin: EdgeInsets.symmetric(vertical: 5),
child: ClipRRect(
borderRadius: BorderRadius.circular(29),
child: RaisedButton(
padding: EdgeInsets.symmetric(vertical: 10),
onPressed: () async {
if (_formKey.currentState.validate()) {
//sign in;
}
},
child: Text("Login")))),
]),
),
);
}
}
2 ответа
Благодаря помощи LOfG ( во Flutter я пытаюсь динамически обновлять виджет столбца на основе текстового контроллера. Как мне это сделать?), Я был вдохновлен ответом.
Следующий код делает следующее:
- Использует StreamBuilder для перестройки с каждым новым событием
- Отображает сообщение под кнопкой входа в систему только в случае сбоя проверки, в противном случае возвращает пустой контейнер
- Когда пользователь нажимает на имя пользователя или пароль, сообщение исчезает, если не пусто
- Использует snapshot.data для входа пользователя в систему (здесь можно использовать Provider с FirebaseAuth)
Причина, по которой мне нужна эта функция, - отображать сообщения об ошибках в определенном месте, а не в текстовых полях.
import 'package:flutter/material.dart';
import 'dart:async';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: TextFieldExample(),
),
),
);
}
}
class TextFieldExample extends StatefulWidget {
@override
_TextFieldExampleState createState() => _TextFieldExampleState();
}
class _TextFieldExampleState extends State<TextFieldExample> {
@override
void dispose() {
_username.close();
_password.close();
super.dispose();
}
String validatorMessage;
bool validate = false; //will be true if the user clicked in the login button
final _username = StreamController<String>(); //stream to validate the text
final _password = StreamController<String>();
Widget build(BuildContext context) {
return Scaffold(
body: Container(
margin: EdgeInsets.symmetric(vertical: 5),
padding: EdgeInsets.symmetric(horizontal: 15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(29),
),
//expose streambuilder to the column widget to use on multiple widgets
child: StreamBuilder<String>(
initialData: '',
stream: _username.stream,
builder: (context, usernameSnapshot) {
return StreamBuilder<String>(
initialData: '',
stream: _password.stream,
builder: (context, passwordSnapshot) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
onChanged: _username.add, //everytime the text changes a new value will be added to the stream
decoration: InputDecoration(
icon: Icon(Icons.person),
hintText: "Email",
),
),
TextField(
obscureText: true,
onChanged: _password.add, //everytime the text changes a new value will be added to the stream
decoration: InputDecoration(
icon: Icon(Icons.visibility),
hintText: "Password",
),
),
Container(
margin: EdgeInsets.symmetric(vertical: 5),
child: ClipRRect(
borderRadius: BorderRadius.circular(29),
child: RaisedButton(
padding: EdgeInsets.symmetric(vertical: 10),
//when user presses button, validate turns to true and we check snapshots to get the data for the entries
onPressed: () async {
if (usernameSnapshot.data.isNotEmpty && passwordSnapshot.data.isNotEmpty) {
try {
//sign in with usernameSnapshot.data and passwordSnapshot.data
} catch (e) {
print(e);
}
validate = true;
} else {
setState(() {
validate = true;
});
}
},
child: Text("Login"),
),
),
),
if (usernameSnapshot.data.isEmpty &&validate == true) //checking the stream and if the user clicked in the button
Container(
alignment: Alignment.center,
child: Text(
'Check your e-mail and password.',
style: TextStyle(
color: Colors.red,
),
),
)
else
Container()
],
);
});
})),
);
}
}
Рассмотрите возможность использования Row
вместо того Column
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: TextFieldExample(),
),
),
);
}
}
class TextFieldExample extends StatefulWidget {
@override
_TextFieldExampleState createState() => _TextFieldExampleState();
}
class _TextFieldExampleState extends State<TextFieldExample> {
final _formKey = GlobalKey<FormState>();
String email = '';
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(vertical: 5),
padding: EdgeInsets.symmetric(horizontal: 15),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(29)),
child: Form(
key: _formKey,
child: Row(children: <Widget>[
Flexible(
child: TextFormField(
validator: (value) => value.isEmpty ? 'Enter an email' : null,
onChanged: (value) {
setState(() => email = value);
},
decoration:
InputDecoration(icon: Icon(Icons.person), hintText: "Email"),
),
),
Expanded(
flex: 0,
child: Container(
margin: EdgeInsets.symmetric(vertical: 5),
child: ClipRRect(
borderRadius: BorderRadius.circular(29),
child: RaisedButton(
padding: EdgeInsets.symmetric(vertical: 10),
onPressed: () async {
if (_formKey.currentState.validate()) {
//sign in;
}
},
child: Text("Login")))),)
]),
),
);
}
}