Question:
I’m working on a login screen in Flutter with two TextFormField
widgets for the username and password. I want to display a red border and red hint text for both fields whenever there’s a validation error or an error returned from the server API.
Expected Behavior:
- Validation Errors: If the user submits the form with empty fields, both the username and password fields should show a red border and red hint text.
- API Errors: If the server returns an error indicating invalid credentials (e.g., “Invalid username or password”), both fields should also show a red border and red hint text.
Actual Behavior:
- Only the password field displays the red border and red hint text on error, but the username field does not update.
What I’ve Tried:
- I’ve ensured that both fields have the
errorText
property passed to theInputDecoration
. - I’ve checked the validation logic to make sure it’s applied to both fields.
Code Snippets:
Below is the relevant part of my code:
Login Screen:
class LogInScreen extends ConsumerStatefulWidget {
// Class implementation
Future<void> _onSubmit() async {
setState(() {
_errorMessage = null;
_usernameError = null;
_passwordError = null;
});
if (!_formKey.currentState!.validate()) return;
final name = _userNameController.text;
final password = _passwordController.text;
try {
await ref.read(authProvider).login(name, password, rememberMe: _rememberMe);
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => const HomeScreen()));
} catch (e) {
setState(() {
_errorMessage = e.toString();
if (e.toString().contains('username')) {
_usernameError = e.toString();
} else if (e.toString().contains('password')) {
_passwordError = e.toString();
}
});
}
}
@override
Widget build(BuildContext context) {
// Build method implementation
TextFieldWidget.buildTextField(
controller: _userNameController,
fieldTitle: 'Username',
hintText: 'Enter Username',
context: context,
),
const SizedBox(height: 20.0),
TextFieldWidget.buildTextField(
controller: _passwordController,
fieldTitle: 'Password',
hintText: 'Enter Password',
obscureText: obscureText,
context: context,
toggleVisibility: () {
ref
.read(
obscureTextProvider.notifier)
.toggle();
},
),
// Similarly for password field
}
}
TextField Widget:
class TextFieldWidget {
static Widget buildTextField({
required TextEditingController controller,
required String fieldTitle,
required String hintText,
bool isPassword = false,
required BuildContext context,
bool obscureText = false,
void Function()? toggleVisibility,
String? errorText,
}) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
fieldTitle,
style: TextStyle(color: Colors.black),
),
const SizedBox(height: 8.0),
TextFormField(
controller: controller,
obscureText: obscureText,
decoration: InputDecoration(
hintText: hintText,
hintStyle: TextStyle(
color: errorText != null ? Colors.red : Colors.grey,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: errorText != null ? Colors.red : Colors.transparent,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
color: errorText != null ? Colors.red : Colors.black,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
color: errorText != null ? Colors.red : Colors.transparent,
),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter $fieldTitle';
}
return null;
},
style: TextStyle(
color: errorText != null ? Colors.red : null,
),
),
],
),
);
}
}
AuthService:
class AuthService extends ChangeNotifier {
Future<void> login(
String username,
String password, {
required bool rememberMe,
}) async {
final url = Uri.parse('${AppConstants.baseURL}/auth/login');
final response = await http.post(
url,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: jsonEncode({
'username': username,
'password': password,
}),
);
if (response.statusCode == 401) {
throw Exception('The email or password you entered did not match our records. Please try again!');
} else if (response.statusCode != 200) {
final responseData = jsonDecode(response.body);
final errorMessage = responseData['error'] ?? responseData['message'] ?? 'Unknown error';
throw Exception(errorMessage);
}
// Rest of the logic
}
}
Question:
How can I ensure that both fields (username and password) correctly display the red border and hint text for both validation and API errors? What might be causing the username field to not update the UI properly when an error occurs?