Summary

I was building an app in flutter where I was trying to keep 3 functions linked to one build function, so I have a navigation bar to navigate between these 3 functions (containing the layout).

Issue

While developing the 3rd layout which is basically a form, where I am unable to change the icon wrapped with GestureDetector(Function:- to toggle the visibility of password, hence changing the icon 👁).

Complete Code

class _MyHomePageState extends State<MyHomePage> { final globalKey = GlobalKey<ScaffoldState>(); final _passwordTextController = TextEditingController(); bool _hidePassword = true; //initial assigning value List<Widget>? pages; int _currentIndex = 0; void initState() { super.initState(); _hidePassword = true; //calling at the starting of the app pages = <Widget>[ homeNavPage(context), servicePage(), manageUser(context), //function I am facing ]; } void _onItemTapped(int index) { setState(() { MyHomePage._selectedIndex = index; }); } @override Widget build(BuildContext context) { var _keyboardVisible = MediaQuery.of(context).viewInsets.bottom != 0; return Scaffold( body: pages![_currentIndex], //navigation bar floatingActionButtonLocation: FloatingActionButtonLocation.miniCenterFloat, floatingActionButton: (!_keyboardVisible) ? Container( clipBehavior: Clip.hardEdge, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(10))), margin: EdgeInsets.only(left: 15.0, right: 15.0), child: NavigationBarTheme( data: NavigationBarThemeData( indicatorColor: Colors.cyan.withOpacity(0.5), labelTextStyle: MaterialStateProperty.all(const TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Colors.white, ))), child: NavigationBar( backgroundColor: Colors.black.withOpacity(0.8), selectedIndex: _currentIndex, animationDuration: const Duration(seconds: 1), labelBehavior: NavigationDestinationLabelBehavior.alwaysShow, onDestinationSelected: (int newIndex) { setState(() { _currentIndex = newIndex; }); }, destinations: [ NavigationDestination( selectedIcon: Icon(Icons.home_rounded, color: Colors.white), icon: Icon(Icons.home_outlined, color: Colors.white), label: 'Home', ), NavigationDestination( selectedIcon: Icon(Icons.design_services, color: Colors.white), icon: Icon(Icons.design_services_outlined, color: Colors.white), label: 'Service', ), NavigationDestination( selectedIcon: Icon(Icons.manage_accounts_rounded, color: Colors.white), icon: Icon(Icons.manage_accounts_outlined, color: Colors.white), label: 'Manage Profile', ), ], ), ), ) : Container(), ); } //1 Stack homeNavPage(BuildContext context) { //Some codes... } //2 servicePage() { //some codes... } //3 function which is creating issue manageUser(BuildContext context) { void _togglePasswordView() { setState(() { _hidePassword = !_hidePassword; }); } // while testing this only runs at the starting print('outside $_hidePassword'); final _formkey = GlobalKey<FormState>(); return Scaffold( body: SingleChildScrollView( child: Container( color: Colors.white, child: Column( children: [ Padding( padding: const EdgeInsets.all(40.0), child: Form( key: _formkey, child: Column( children: [ // some extra codes TextFormField( keyboardType: TextInputType.text, decoration: InputDecoration( floatingLabelBehavior: FloatingLabelBehavior.never, icon: Icon(Icons.password), // hintText: 'Enter your password', labelText: 'Password', suffixIcon: GestureDetector( //works as usual whenever toggle happens onTap: () { print('1. $_hidePassword'); _togglePasswordView(); print('2. $_hidePassword'); }, //icon doesn't change child: Icon(_hidePassword == true ? Icons.visibility_off : Icons.visibility), ), ), controller: _passwordTextController, // focusNode: _focusPassword, //no changes happens obscureText: _hidePassword, validator: (value) => Validator.validatePassword(password: value!), ), ], ), ), ), ], ), ), ), ); } } 

Code Specific Having Issue

 TextFormField( //some codes suffixIcon: GestureDetector( //works as usual whenever toggle happens onTap: () { print('1. $_hidePassword'); _togglePasswordView(); print('2. $_hidePassword'); }, //icon doesn't change child: Icon(_hidePassword == true ? Icons.visibility_off : Icons.visibility), ), ), //no changes happens obscureText: _hidePassword, ), 

Testing

I have performed testing by using the 3 print statement where,

  1. print('outside $_hidePassword'); = runs once during initialization.
  2. other 2 print statements present inside the onTap: () of GestureDetector works as usual changing the values.
  3. But there is no change found in the icon and obscureText state.

Screenshot

issue SS

Already specified all the details, if required please ask, and I will furnish you with other details.

Thank you.

3 Answers

You need to update state to change icon but calling setState is to expensive.So use StatefulBuilder like this:

StatefulBuilder(builder: (context, innerSetState) { return TextFormField( //some codes decoration: InputDecoration( suffixIcon: GestureDetector( onTap: () { print('1. $_hidePassword'); innerSetState(() { _hidePassword = !_hidePassword; }); print('2. $_hidePassword'); }, child: Icon(_hidePassword == true ? Icons.visibility_off : Icons.visibility), ), ), obscureText: _hidePassword, ); }), 
8

You are breaking the context chain by creating variables on initState.

 pages = <Widget>[ manageUser(context), ..] 

So the page initially get setup. While you are not reassign the page, the pages stay the same. You can reassign the page, on _togglePasswordView, that will update the visibility.

So it can be

void _togglePasswordView() { setState(() { _hidePassword = !_hidePassword; }); pages = <Widget>[ // assinging pages again after chaning visibilty manageUser(context), ]; } 

But it would be nice to convert pages variable to method. So whenever state changes, build method will trigger and widgets will rebuild with update value.

 List<Widget> setUpPages() { return <Widget>[ manageUser(context), ]; } .... body: setUpPages()[_currentIndex], 
 class _MyHomePageState extends State<MyHomePage> { final globalKey = GlobalKey<ScaffoldState>(); final _passwordTextController = TextEditingController(); bool _hidePassword = true; //initial assigning value int _currentIndex = 0; @override void initState() { super.initState(); _hidePassword = true; //calling at the starting of the app } List<Widget> setUpPages() { return <Widget>[ manageUser(context), ]; } @override Widget build(BuildContext context) { return Scaffold( body: setUpPages()[_currentIndex], //navigation bar floatingActionButtonLocation: FloatingActionButtonLocation.miniCenterFloat, floatingActionButton: Container( child: NavigationBarTheme( data: NavigationBarThemeData( indicatorColor: Colors.cyan.withOpacity(0.5), labelTextStyle: MaterialStateProperty.all(const TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Colors.white, ))), child: SizedBox()), )); } //3 function which is creating issue manageUser(BuildContext context) { void _togglePasswordView() { setState(() { _hidePassword = !_hidePassword; }); } // while testing this only runs at the starting print('outside $_hidePassword'); final _formkey = GlobalKey<FormState>(); return Scaffold( body: SingleChildScrollView( child: Container( color: Colors.white, child: Column( children: [ Padding( padding: const EdgeInsets.all(40.0), child: Form( key: _formkey, child: Column( children: [ // some extra codes TextFormField( keyboardType: TextInputType.text, decoration: InputDecoration( floatingLabelBehavior: FloatingLabelBehavior.never, icon: Icon(Icons.password), // hintText: 'Enter your password', labelText: 'Password', suffixIcon: GestureDetector( //works as usual whenever toggle happens onTap: () { print('1. $_hidePassword'); _togglePasswordView(); print('2. $_hidePassword'); }, //icon doesn't change child: Icon(_hidePassword == true ? Icons.visibility_off : Icons.visibility), ), ), controller: _passwordTextController, // focusNode: _focusPassword, //no changes happens obscureText: _hidePassword, ), ], ), ), ), ], ), ), ), ); } } 
6

Try below code

bool variable:

bool passObscureText = true; 

Your Toggle function:

void passToggle() { setState(() { passObscureText = !passObscureText; }); } 

Your Widget.

 TextFormField( obscureText: passObscureText, decoration: InputDecoration( isDense: true, border: const OutlineInputBorder(), suffixIcon: IconButton( onPressed: passToggle, icon: Icon( passObscureText ? Icons.visibility_off_outlined : Icons.visibility, ), ), hintText: 'Password', ), ), 

Result Screen before button press-> image

Result Screen after button press-> image

1

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.