1

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.

Md. Yeasin Sheikh
  • 54,221
  • 7
  • 29
  • 56
Spsnamta
  • 479
  • 6
  • 11

3 Answers3

1

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,
          );
        }),
eamirho3ein
  • 16,619
  • 2
  • 12
  • 23
  • 1
    Thanks @eamirho3ein , your solution solved my issue(as per the screenshot). but you missed one case that is the obscureText which was not working in your provided solution, but I managed to wrap the whole TextFormField, hence it worked, it will be better if you update the code. – Spsnamta Sep 02 '22 at 14:39
  • 1
    Glad it helps you and thank you for noticing that, I updated my answer. – eamirho3ein Sep 02 '22 at 14:45
  • 1
    Thank you for editing back on my request. – Spsnamta Sep 02 '22 at 14:50
  • Actually, this is not the issue, @eamirho3ein why do we need `StatefulBuilder` inside statefulWidget? While both providing the state. – Md. Yeasin Sheikh Sep 02 '22 at 14:56
  • some times like here, it is too expensive to setstate the page. So we use StatefulBuilder to just update a pice of widget. [more information](https://api.flutter.dev/flutter/widgets/StatefulBuilder-class.html) – eamirho3ein Sep 02 '22 at 15:03
  • Are you certain about this? If we have animation and others expensive widget, we create separate context. If you need to update another widget of the page, should we again wrap with `StatefulBuilder`? It means to update N widget we need to N `StatefulBuilder`, even though we are using statefulWidget. But the issue is here about using a variable that is not changing. Sorry but your approach seems expensive and extra work to me having extra N states. – Md. Yeasin Sheikh Sep 02 '22 at 15:10
  • @YeasinSheikh There is no certain answer for every issue.Every one have a different solution and every solution has its own pros and cons.So If you are not accept it I appreciate it. – eamirho3ein Sep 02 '22 at 15:15
  • My concern was to understand the issue, like if `_hidePassword` used on another method another question will raise, [this may help](https://stackoverflow.com/q/56445912/10157127) – Md. Yeasin Sheikh Sep 02 '22 at 15:33
1

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,
                      ),
                    ],
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
Md. Yeasin Sheikh
  • 54,221
  • 7
  • 29
  • 56
  • Thanks @Yeasin Sheikh for answering my issue in a elaborative way, you have provided 2 ways to solved. – Spsnamta Sep 02 '22 at 17:05
  • 1. by putting the pages inside the setState() 2. creating a function out of the pages Among these two solutions, 1 was quite the appropriate approach, but in the case of 2, I was unable to open the keyboard, if possible please try to figure it out and update the code. – Spsnamta Sep 02 '22 at 17:13
  • 1
    I've removed others snippet like `_keyboardVisible` just to focus on visibility issue, you can follow the second approach. – Md. Yeasin Sheikh Sep 02 '22 at 17:26
  • It worked, But without removing it, is there any way to make it work? – Spsnamta Sep 02 '22 at 17:36
  • I mean I've removed those to make it specific, you can have based on your need, Those should along with theses, Sorry I can't test with keyboard being some limitation, [This example will give you some hint](https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html) e – Md. Yeasin Sheikh Sep 02 '22 at 17:45
  • Thanks for sharing the [example](https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html), but this doesn't meet any related to BottomNavigationBar hiding while filling the form, which I have used in my case. – Spsnamta Sep 04 '22 at 08:00
0

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

Ravindra S. Patil
  • 11,757
  • 3
  • 13
  • 40
  • Thanks, @Ravindra S. Patil for addressing the issue, but I have already tried this approach, and as per my description I was having a state-changing issue comprising of (initstate, setState()), which is already solved. – Spsnamta Sep 02 '22 at 17:19