Reputation: 13
I try to use FocusNode() in TextFormField so it will detect when I focus on the TextFormField, the text above will have it's color changed. But it seems not working, what seems to be the problem? enter image description here
import 'package:flutter/material.dart';
class SignIn extends StatefulWidget {
const SignIn({Key? key}) : super(key: key);
@override
_SignInState createState() => _SignInState();
}
class _SignInState extends State<SignIn> {
bool isPasswordVisible = false;
bool _isEmail = true;
bool _isPassword = true;
TextEditingController emailInput = TextEditingController();
TextEditingController passwordInput = TextEditingController();
FocusNode emailText = FocusNode();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SingleChildScrollView(
child: Container(
padding: EdgeInsets.all(30.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Sign In to Coinify',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 24.0,
fontFamily: "Graphik",
)
),
SizedBox(height: 20.0),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Email',
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
color: emailText.hasFocus ? Colors.blue : Colors.black,
fontFamily: "Graphik",
),
),
SizedBox(height: 8.0),
TextFormField(
focusNode: emailText,
controller: emailInput,
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
border: OutlineInputBorder(),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey)
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue)
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red)
),
hintText: 'Enter your email',
errorText: _isEmail ? null : "Email must not be empty",
),
)
],
),
SizedBox(height: 20.0),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Password',
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
fontFamily: "Graphik",
),
),
SizedBox(height: 8.0),
TextFormField(
controller: passwordInput,
obscureText: !isPasswordVisible,
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
border: OutlineInputBorder(),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey)
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue)
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
hintText: 'Enter your password',
errorText: _isPassword ? null : "Password must not be empty",
suffixIcon: GestureDetector(
onTap: () {
setState(() {
isPasswordVisible = !isPasswordVisible;
});
},
child: Icon(
isPasswordVisible ? Icons.visibility : Icons.visibility_off,
),
),
),
)
],
),
SizedBox(height: 50.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
Navigator.pushNamed(context, '/forgot-password');
},
child: Text(
'Forgot password?',
style: TextStyle(
color: Colors.blue,
)
)
)
),
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
Navigator.pushNamed(context, '/privacy-policy');
},
child: Text(
'Privacy policy',
style: TextStyle(
color: Colors.blue,
)
)
)
),
]
),
SizedBox(height: 20.0),
ElevatedButton(
onPressed: () {
setState(() {
if(emailInput.text == ""){_isEmail = false;}
else{_isEmail = true;}
if(passwordInput.text == ""){_isPassword = false;}
else{_isPassword = true;}
if(_isEmail && _isPassword) {Navigator.pushNamed(context, '/sign-in-code');}
});
},
child: Text(
"Sign in",
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.normal,
color: Colors.white
),
),
style: ButtonStyle(
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0))
),
minimumSize: MaterialStateProperty.all(Size.fromHeight(60.0)),
backgroundColor: MaterialStateProperty.all(Colors.blue),
shadowColor: MaterialStateProperty.all(Colors.transparent)
),
)
]
)
),
)
);
}
}
I have tried to change the color in the ternary operator and realized that the Color seems to stuck in false value
Upvotes: 0
Views: 105
Reputation: 31
This is happening because you are not updating the state and resulting in ui change. Use Focus
widget and setState in onFocusChange
callback.
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _isNodeFocus = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text("My app"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Email',
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
color: _isNodeFocus ? Colors.blue : Colors.yellow,
fontFamily: "Graphik",
),
),
Focus(
onFocusChange: (hasFocus) {
setState(() {
_isNodeFocus = hasFocus;
});
},
child: TextFormField(),
)
],
),
),
);
}
}
Upvotes: 0
Reputation: 626
The problem is because your UI is not rebuild when you focus the TextField.
You must call the setState()
to update your UI.
FocusNode
Since you have already creating the focus node and set color in the ternary operator, you just need to add the following code:
class _SignInState extends State<SignIn> {
...
FocusNode emailText = FocusNode();
...
// add listener to your FocusNode
@override
void initState() {
super.initState();
emailText.addListener(_onEmailFocusChange);
}
// the listener action is setting state when focus change
void _onEmailFocusChange() {
setState(() {});
debugPrint("Email is focus: ${emailText.hasFocus.toString()}");
}
// don't forget to dispose your listener
@override
void dispose() {
super.dispose();
emailText.removeListener(_onEmailFocusChange);
emailText.dispose();
}
@override
Widget build(BuildContext context) {
...
...
...
}
}
And this is your final code:
import 'package:flutter/material.dart';
class SignIn extends StatefulWidget {
const SignIn({Key? key}) : super(key: key);
@override
_SignInState createState() => _SignInState();
}
class _SignInState extends State<SignIn> {
bool isPasswordVisible = false;
bool _isEmail = true;
bool _isPassword = true;
TextEditingController emailInput = TextEditingController();
TextEditingController passwordInput = TextEditingController();
FocusNode emailText = FocusNode();
@override
void initState() {
super.initState();
emailText.addListener(_onEmailFocusChange);
}
void _onEmailFocusChange() {
setState(() {});
debugPrint("Email is focus: ${emailText.hasFocus.toString()}");
}
@override
void dispose() {
super.dispose();
emailText.removeListener(_onEmailFocusChange);
emailText.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SingleChildScrollView(
child: Container(
padding: EdgeInsets.all(30.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Sign In to Coinify',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 24.0,
fontFamily: "Graphik",
)),
SizedBox(height: 20.0),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Email',
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
color:
emailText.hasFocus ? Colors.blue : Colors.black,
fontFamily: "Graphik",
),
),
SizedBox(height: 8.0),
TextFormField(
focusNode: emailText,
controller: emailInput,
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
vertical: 10, horizontal: 10),
border: OutlineInputBorder(),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue)),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red)),
hintText: 'Enter your email',
errorText:
_isEmail ? null : "Email must not be empty",
),
)
],
),
SizedBox(height: 20.0),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Password',
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
fontFamily: "Graphik",
),
),
SizedBox(height: 8.0),
TextFormField(
controller: passwordInput,
obscureText: !isPasswordVisible,
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
vertical: 10, horizontal: 10),
border: OutlineInputBorder(),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue)),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
hintText: 'Enter your password',
errorText: _isPassword
? null
: "Password must not be empty",
suffixIcon: GestureDetector(
onTap: () {
setState(() {
isPasswordVisible = !isPasswordVisible;
});
},
child: Icon(
isPasswordVisible
? Icons.visibility
: Icons.visibility_off,
),
),
),
)
],
),
SizedBox(height: 50.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
Navigator.pushNamed(
context, '/forgot-password');
},
child: Text('Forgot password?',
style: TextStyle(
color: Colors.blue,
)))),
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
Navigator.pushNamed(
context, '/privacy-policy');
},
child: Text('Privacy policy',
style: TextStyle(
color: Colors.blue,
)))),
]),
SizedBox(height: 20.0),
ElevatedButton(
onPressed: () {
setState(() {
if (emailInput.text == "") {
_isEmail = false;
} else {
_isEmail = true;
}
if (passwordInput.text == "") {
_isPassword = false;
} else {
_isPassword = true;
}
if (_isEmail && _isPassword) {
Navigator.pushNamed(context, '/sign-in-code');
}
});
},
child: Text(
"Sign in",
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.normal,
color: Colors.white),
),
style: ButtonStyle(
shape:
MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(5.0))),
minimumSize:
MaterialStateProperty.all(Size.fromHeight(60.0)),
backgroundColor:
MaterialStateProperty.all(Colors.blue),
shadowColor:
MaterialStateProperty.all(Colors.transparent)),
)
])),
));
}
}
Focus
widgetThe second method is more simple, it just only wrap your TextFormField
with Focus
widget and call the setState
in the onFocusChange
like below:
Focus( // <- wrap with this
onFocusChange: (focus) {
setState(() {}); // <- and call setState
print("Email is focus: $focus");
},
child: TextFormField(
focusNode: emailText,
controller: emailInput,
decoration: InputDecoration(
...
),
),
),
And this is your final code using this method:
import 'package:flutter/material.dart';
class SignIn extends StatefulWidget {
const SignIn({Key? key}) : super(key: key);
@override
_SignInState createState() => _SignInState();
}
class _SignInState extends State<SignIn> {
bool isPasswordVisible = false;
bool _isEmail = true;
bool _isPassword = true;
TextEditingController emailInput = TextEditingController();
TextEditingController passwordInput = TextEditingController();
FocusNode emailText = FocusNode();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SingleChildScrollView(
child: Container(
padding: EdgeInsets.all(30.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Sign In to Coinify',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 24.0,
fontFamily: "Graphik",
)),
SizedBox(height: 20.0),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Email',
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
color:
emailText.hasFocus ? Colors.blue : Colors.black,
fontFamily: "Graphik",
),
),
SizedBox(height: 8.0),
Focus(
onFocusChange: (focus) {
setState(() {});
print("Email is focus: $focus");
},
child: TextFormField(
focusNode: emailText,
controller: emailInput,
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
vertical: 10, horizontal: 10),
border: OutlineInputBorder(),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue)),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red)),
hintText: 'Enter your email',
errorText:
_isEmail ? null : "Email must not be empty",
),
),
),
],
),
SizedBox(height: 20.0),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Password',
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
fontFamily: "Graphik",
),
),
SizedBox(height: 8.0),
TextFormField(
controller: passwordInput,
obscureText: !isPasswordVisible,
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
vertical: 10, horizontal: 10),
border: OutlineInputBorder(),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue)),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
hintText: 'Enter your password',
errorText: _isPassword
? null
: "Password must not be empty",
suffixIcon: GestureDetector(
onTap: () {
setState(() {
isPasswordVisible = !isPasswordVisible;
});
},
child: Icon(
isPasswordVisible
? Icons.visibility
: Icons.visibility_off,
),
),
),
)
],
),
SizedBox(height: 50.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
Navigator.pushNamed(
context, '/forgot-password');
},
child: Text('Forgot password?',
style: TextStyle(
color: Colors.blue,
)))),
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
Navigator.pushNamed(
context, '/privacy-policy');
},
child: Text('Privacy policy',
style: TextStyle(
color: Colors.blue,
)))),
]),
SizedBox(height: 20.0),
ElevatedButton(
onPressed: () {
setState(() {
if (emailInput.text == "") {
_isEmail = false;
} else {
_isEmail = true;
}
if (passwordInput.text == "") {
_isPassword = false;
} else {
_isPassword = true;
}
if (_isEmail && _isPassword) {
Navigator.pushNamed(context, '/sign-in-code');
}
});
},
child: Text(
"Sign in",
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.normal,
color: Colors.white),
),
style: ButtonStyle(
shape:
MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(5.0))),
minimumSize:
MaterialStateProperty.all(Size.fromHeight(60.0)),
backgroundColor:
MaterialStateProperty.all(Colors.blue),
shadowColor:
MaterialStateProperty.all(Colors.transparent)),
)
])),
));
}
}
And this is the result:
Hopefully it can solve your problem, Thanks 😉
Upvotes: 0
Reputation: 116
You may add the setState
method as a listener
to the FocusNode
object named emailText
. This will change the color of the Text you want to change based on the focus on the TextField.
Modified code:
class _SignInState extends State<SignIn> {
bool isPasswordVisible = false;
bool _isEmail = true;
bool _isPassword = true;
TextEditingController emailInput = TextEditingController();
TextEditingController passwordInput = TextEditingController();
FocusNode emailText = FocusNode();
@override
void initState() {
super.initState();
emailText.addListener(() => setState(() {}));
}
// Rest of your code
}
Upvotes: 0
Reputation: 216
You should add a listener to focusNode value to detect where the TextFormField is being focused or not.
I prefer to use ValueNotifier and ValueListenableBuilder to rebuild only the Text widget not all the widgets in the screen.
final isEmailFocused = ValueNotifier<bool>(false);
@override
void initState() {
super.initState();
emailText.addListener(() {
isEmailFocused.value = emailText.hasFocus;
});
}
@override
void dispose() {
// don't forget to dispose all the inputs notifiers.
emailText.dispose();
emailInput.dispose();
isEmailFocused.removeListener(() {});
isEmailFocused.dispose();
super.dispose();
}
In Build method I wrapped the Email Text Widget with ValueListenableBuilder widget to rebuild the widget if the emailInput is focused.
ValueListenableBuilder(
valueListenable: isEmailFocused,
builder: (BuildContext context, bool value, Widget? child) {
return Text(
'Email',
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
color: value ? Colors.blue : Colors.black,
fontFamily: "Graphik",
),
);
},
),
Upvotes: 0
Reputation: 1220
The state is not changing when the focus is changed, therefore the text color doesn't change.
You must listen to the FocusNode
changes and setState
after any changes. Update your widget like this:
FocusNode emailText = FocusNode();
Color _emailTextColor = Colors.black;
_onEmailFocusChange() {
setState(() {
_emailTextColor = emailText.hasFocus ? Colors.blue : Colors.black;
});
}
@override
void initState() {
emailText.addListener(_onEmailFocusChange);
super.initState();
}
@override
void dispose() {
super.dispose();
emailText.removeListener(_onEmailFocusChange);
emailText.dispose();
}
Use _emailTextColor
as your text color inside TextStyle
.
Upvotes: 0