154

Currently I have my password TextFormField like this:

TextFormField(
  decoration: const InputDecoration(
      labelText: 'Password',
      icon: const Padding(
        padding: const EdgeInsets.only(top: 15.0),
        child: const Icon(Icons.lock),
      )),
  validator: (val) => val.length < 6 ? 'Password too short.' : null,
  onSaved: (val) => _password = val,
  obscureText: true,
);

I want a button like interaction which will make password visible and invisible. Can I do it inside TextFormField? Or I will have to make a Stack widget to get my required UI. And how will the condition be made regarding obscureText true/false?

Ikar Pohorský
  • 4,617
  • 6
  • 39
  • 56
Farwa
  • 6,156
  • 8
  • 31
  • 46

15 Answers15

289

I have created a solution as per @Hemanth Raj but in a more robust way.

First declare a bool variable _passwordVisible.

Initiate _passwordVisible to false in initState()

@override
  void initState() {
    _passwordVisible = false;
  }

Following is the TextFormField widget :

TextFormField(
   keyboardType: TextInputType.text,
   controller: _userPasswordController,
   obscureText: !_passwordVisible,//This will obscure text dynamically
   decoration: InputDecoration(
       labelText: 'Password',
       hintText: 'Enter your password',
       // Here is key idea
       suffixIcon: IconButton(
            icon: Icon(
              // Based on passwordVisible state choose the icon
               _passwordVisible
               ? Icons.visibility
               : Icons.visibility_off,
               color: Theme.of(context).primaryColorDark,
               ),
            onPressed: () {
               // Update the state i.e. toogle the state of passwordVisible variable
               setState(() {
                   _passwordVisible = !_passwordVisible;
               });
             },
            ),
          ),
        );
Christopher Moore
  • 15,626
  • 10
  • 42
  • 52
Parikshit Chalke
  • 3,745
  • 2
  • 16
  • 26
  • 7
    in the default Normal cases, on page load password is obscured so would initiate passwordVisible as true i.e ```passwordVisible= true``` , and to make it make sense in this case replace variable ```passwordVisible``` with ```passwordObscured= true``` – Nelson Bwogora Dec 01 '19 at 20:21
  • 5
    Adding to what nelson pointed out, a simple fix to this is doing `obscureText: !passwordVisible` – Taha Rushain Jan 25 '20 at 10:11
  • thanks this worked for me..! but the validator is not working – Ananthakrishna Oct 21 '20 at 14:55
  • Nice and easy, thanks for this! Also to note: doesn't have to be a Stateful widget, can be Stateless and have it rebuilt from a Stateful screen widget. – sleepystar96 Dec 15 '22 at 08:01
145

First make you widget StatefulWidget if it is a StatelessWidget.

Then have a variable bool _obscureText and pass it to your TextFormField. The toggle it with setState as required.

Example:

class _FormFieldSampleState extends State<FormFieldSample> {

  // Initially password is obscure
  bool _obscureText = true;

  String _password;

  // Toggles the password show status
  void _toggle() {
    setState(() {
      _obscureText = !_obscureText;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Sample"),
      ),
      body: new Container(
        child: new Column(
          children: <Widget>[
            new TextFormField(
              decoration: const InputDecoration(
                  labelText: 'Password',
                  icon: const Padding(
                      padding: const EdgeInsets.only(top: 15.0),
                      child: const Icon(Icons.lock))),
              validator: (val) => val.length < 6 ? 'Password too short.' : null,
              onSaved: (val) => _password = val,
              obscureText: _obscureText,
            ),
            new FlatButton(
                onPressed: _toggle,
                child: new Text(_obscureText ? "Show" : "Hide"))
          ],
        ),
      ),
    );
  }
}
starball
  • 20,030
  • 7
  • 43
  • 238
Hemanth Raj
  • 32,555
  • 10
  • 92
  • 82
  • Thanks for the help. I used Stack instead of container. So the password overlaps with the button. I put padding inside FlatButton container. Still overlapping. Any idea how to resolve that? – Farwa Mar 06 '18 at 07:32
  • 1
    But why stack? Try `Row` for horizontal arrangement and `Column` for vertical arrangement – Hemanth Raj Mar 06 '18 at 07:34
  • Here's how the design will be: https://i.stack.imgur.com/q8aQU.png Instead of the icon it will be the FlatButton. As because it's top of the TextFormField I used Stack. – Farwa Mar 06 '18 at 07:47
  • 33
    There is no need of Stack. `InputDecoration` has a [`suffixIcon`](https://docs.flutter.io/flutter/material/InputDecoration/suffixIcon.html) property which might be what you are looking for exactly. – Hemanth Raj Mar 06 '18 at 07:56
  • Seems Stack is still needed for Cupertino? suffixIcon appears to only work for Material. – Pete Alvin Mar 13 '20 at 22:46
  • Yes, we can use suffixIcon attribute of InputDecoration for TextFormField eg. decoration: InputDecoration( suffixIcon: IconButton( icon: Icon((_isPasswordVisible) ? Icons.visibility : Icons.visibility_off), onPressed: (){ }, ),) – Kamlesh May 12 '21 at 07:21
20

This solution prevents taps in the suffixIcon from giving focus to the TextField, but if the TextField was focused and user wants to reveal/hide the password, then focus is not lost.

Result

import 'package:flutter/material.dart';

class PasswordField extends StatefulWidget {
  const PasswordField({Key? key}) : super(key: key);

  @override
  _PasswordFieldState createState() => _PasswordFieldState();
}

class _PasswordFieldState extends State<PasswordField> {
  final textFieldFocusNode = FocusNode();
  bool _obscured = false;

  void _toggleObscured() {
    setState(() {
      _obscured = !_obscured;
      if (textFieldFocusNode.hasPrimaryFocus) return; // If focus is on text field, dont unfocus
      textFieldFocusNode.canRequestFocus = false;     // Prevents focus if tap on eye
    });
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      keyboardType: TextInputType.visiblePassword,
      obscureText: _obscured,
      focusNode: textFieldFocusNode,
      decoration: InputDecoration(
        floatingLabelBehavior: FloatingLabelBehavior.never, //Hides label on focus or if filled
        labelText: "Password",
        filled: true, // Needed for adding a fill color
        fillColor: Colors.grey.shade800, 
        isDense: true,  // Reduces height a bit
        border: OutlineInputBorder(
          borderSide: BorderSide.none,              // No border
          borderRadius: BorderRadius.circular(12),  // Apply corner radius
        ),
        prefixIcon: Icon(Icons.lock_rounded, size: 24),
        suffixIcon: Padding(
          padding: const EdgeInsets.fromLTRB(0, 0, 4, 0),
          child: GestureDetector(
            onTap: _toggleObscured,
            child: Icon(
              _obscured
                  ? Icons.visibility_rounded
                  : Icons.visibility_off_rounded,
              size: 24,
            ),
          ),
        ),
      ),
    );
  }
}
Bugzilla
  • 1,856
  • 1
  • 10
  • 16
  • I like your solution. Nevertheless instead of using GestureDetector and Icon, you could use IconButton. – clemsciences Feb 17 '22 at 11:01
  • 1
    Yes, the thing is I don’t like the ink effect of IconButton and I know now you can disable it with splash/highlight color properties but I didn’t know that back when I posted this. – Bugzilla Feb 18 '22 at 13:06
18

With a credit goes to X-Wei, you can create the widget as a separate password.dart:

import 'package:flutter/material.dart';

class PasswordField extends StatefulWidget {
  const PasswordField({
    this.fieldKey,
    this.hintText,
    this.labelText,
    this.helperText,
    this.onSaved,
    this.validator,
    this.onFieldSubmitted,
  });

  final Key fieldKey;
  final String hintText;
  final String labelText;
  final String helperText;
  final FormFieldSetter<String> onSaved;
  final FormFieldValidator<String> validator;
  final ValueChanged<String> onFieldSubmitted;

  @override
  _PasswordFieldState createState() => new _PasswordFieldState();
}

class _PasswordFieldState extends State<PasswordField> {
  bool _obscureText = true;

  @override
  Widget build(BuildContext context) {
    return new TextFormField(
      key: widget.fieldKey,
      obscureText: _obscureText,
      maxLength: 8,
      onSaved: widget.onSaved,
      validator: widget.validator,
      onFieldSubmitted: widget.onFieldSubmitted,
      decoration: new InputDecoration(
        border: const UnderlineInputBorder(),
        filled: true,
        hintText: widget.hintText,
        labelText: widget.labelText,
        helperText: widget.helperText,
        suffixIcon: new GestureDetector(
          onTap: () {
            setState(() {
              _obscureText = !_obscureText;
            });
          },
          child:
          new Icon(_obscureText ? Icons.visibility : Icons.visibility_off),
        ),
      ),
    );
  }
}

Call it as:

  import 'package:my_app/password.dart';

  String _password;
  final _passwordFieldKey = GlobalKey<FormFieldState<String>>();

  PasswordField(
    fieldKey: _passwordFieldKey,
    helperText: 'No more than 8 characters.',
    labelText: 'Password *',
    onFieldSubmitted: (String value) {
      setState(() {
        this._password = value;
      });
    },
  ),
Hasan A Yousef
  • 22,789
  • 24
  • 132
  • 203
14

Well I personally like to keep the passwords hidden all the time and seen when you want to see them, so this is the approach I use to hide/unhide passwords,Incase you want the password to be visible when the touch is In contact with the hide icon, and hidden as soon as you remove the contact then this is for you

//make it invisible globally
  bool invisible = true;

//wrap your toggle icon in Gesture Detector
  GestureDetector(
   onTapDown: inContact,//call this method when incontact
   onTapUp: outContact,//call this method when contact with screen is removed
   child: Icon(
   Icons.remove_red_eye,
   color: colorButton,
   ),
  ),

  void inContact(TapDownDetails details) {
    setState(() {
      invisible = false;
    });
  }

  void outContact(TapUpDetails details) {
    setState(() {
      invisible=true;
    });
  }

This approach is being used in of my packages https://pub.dev/packages/passwordfield

The output of the above code

enter image description here

Mahesh Jamdade
  • 17,235
  • 8
  • 110
  • 131
11

I did it with holding and releasing the longTap:

    bool _passwordVisible;

@override
void initState() {
    _passwordVisible = false;
    super.initState();
}

// ...
TextFormField(
  obscureText: !_passwordVisible,
  decoration: InputDecoration(
    hasFloatingPlaceholder: true,
    filled: true,
    fillColor: Colors.white.withOpacity(0.5),
    labelText: "Password",
    suffixIcon: GestureDetector(
      onLongPress: () {
        setState(() {
          _passwordVisible = true;
        });
      },
      onLongPressUp: () {
        setState(() {
          _passwordVisible = false;
        });
      },
      child: Icon(
          _passwordVisible ? Icons.visibility : Icons.visibility_off),
    ),
  ),
  validator: (String value) {
    if (value.isEmpty) {
      return "*Password needed";
    }
  },
  onSaved: (String value) {
    _setPassword(value);
  },
);
Zeniel Chaves
  • 111
  • 1
  • 3
8

○ Just a Simple 3 Steps you can follow and password Show/Hide done.


enter image description here

Step 1:create variable

       bool _isHidden = true;

Step 2: Magical Step, make the icon clickable and see/hide the password.

Now I will wrap the icon with InkWell which will make it clickable. So, when we will click on that it will toggle the obscureText the argument between true and false.

 @override
   Widget build(BuildContext context) {
     return Scaffold(
        backgroundColor: Theme.of(context).secondaryHeaderColor,
          body: Center(
            child: Container(
            height: 55,
            alignment: Alignment.center,
            padding: EdgeInsets.fromLTRB(10, 10, 10, 0),
            child: TextField(
              obscureText: _isHidden,
              decoration: InputDecoration(
                border: OutlineInputBorder(),
                labelText: 'Password',
                suffix: InkWell(
                  onTap: _togglePasswordView,  /// This is Magical Function
                  child: Icon(
                    _isHidden ?         /// CHeck Show & Hide.
                     Icons.visibility :
                     Icons.visibility_off,
                  ),
                ),
                /*icon: Icon(
                  Icons.password_sharp,
                  color: Colors.black,
                ),*/
              ),
            ),
          ),
        ),
     );
  }

Step 3: Create This the Magical Function.

      void _togglePasswordView() {
    setState(() {
        _isHidden = !_isHidden;
    });
}
  

☻♥Done.

4
class SignIn extends StatefulWidget {

  @override
  _SignInState createState() => _SignInState();
}

class _SignInState extends State<SignIn> {

        // Initially password is obscure
  bool _obscureText = true;
    // Toggles the password show status
  void _togglePasswordStatus() {
    setState(() {
      _obscureText = !_obscureText;
    });
  }
  @override
  Widget build(BuildContext context) {
    return 
    Scaffold(
      backgroundColor: Colors.brown[100],
      appBar: AppBar(
        backgroundColor: Colors.brown[400],
        elevation: 0.0,
        title: Text('Sign In'),
      ),
      body: Container(
        padding: EdgeInsets.symmetric(vertical:20.0,horizontal:50.0),
        child: Form(
          key: _formKey,
          child: Column(children: <Widget>[
            TextFormField(
              decoration: InputDecoration(
                hintText: 'Password',
                suffixIcon:  IconButton(
                  icon:Icon(_obscureText ? Icons.visibility:Icons.visibility_off,),
                   onPressed: _togglePasswordStatus,
                   color: Colors.pink[400],
                   ),
              ),
              validator: (val){
                return
                val.length < 6 ? 'Enter A Password Longer Than 6 Charchters' :null;
              },
              obscureText: _obscureText,
              onChanged: (val){
                setState(() {
                  password = val.trim();
                });
              },
            ),
        ],),),
      ),
    );
  }
}
M.hunter
  • 41
  • 1
4

Here's a simpler example with built-in material design icons:

child: TextFormField(
              decoration: InputDecoration(
                  fillColor: Color(0xFFFFFFFF), filled: true,
                  enabledBorder: UnderlineInputBorder(
                    borderSide: BorderSide(color: Color(0xFF808080)),
                  ),
                  suffixIcon: GestureDetector(
                    onTap: () {
                      setState(() {
                        _showPassword = !_showPassword;
                      });
                    },
                    child: Icon(
                        _showPassword ? Icons.visibility : Icons.visibility_off,
                    ),
                  ),
                  labelText: 'Password'),
              obscureText: !_showPassword,
            ),
Martin H.
  • 152
  • 1
  • 12
3
  bool _obscuredText = true; 

  _toggle(){
    setState(() {
      _obscuredText = !_obscuredText;
    });
  }

  Widget _createPassword(){
    return TextField(
      obscureText: _obscuredText,
      cursorColor: Colors.black54,
      style: TextStyle( color: Colors.black54),
      decoration: InputDecoration(
        labelStyle: TextStyle(
            color: Colors.black54
        ),
        focusedBorder: OutlineInputBorder(
            borderSide: BorderSide(
                color: Colors.black54
            )
        ),
        border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(5.0)
        ),
        labelText: 'Contraseña',
        hintText: 'Contraseña',
        suffixIcon: FlatButton(onPressed: _toggle, child:Icon(Icons.remove_red_eye, color: _obscuredText ? Colors.black12 : Colors.black54))
      ),
      onChanged: (value) {
        setState(() {
          _password = value;
        });
      },
    );
  }

Hope this helps!

OmarACY
  • 39
  • 3
1
//Password
const TextField(
  obscureText: true, //for hide Password
  decoration: InputDecoration(
      prefixIcon: Icon(Icons.lock_outline),
      hintText: 'Password'),
),
General Grievance
  • 4,555
  • 31
  • 31
  • 45
Sarthak Raval
  • 1,001
  • 1
  • 10
  • 23
1

This is my Widget with Getx

Widget build(BuildContext context) {
RxBool  passwordVisible = false.obs;
return
  Obx(() => TextFormField(
    obscureText: !passwordVisible.value,
    decoration: InputDecoration(
        floatingLabelBehavior: FloatingLabelBehavior.never,
        suffixIcon: IconButton(
        icon: Icon(
          passwordVisible.value
              ? Icons.visibility
              : Icons.visibility_off,
          color: Colors.black,
          size: 20,
        ),
          onPressed: (){
            passwordVisible.value = !passwordVisible.value;
          },
      ),
        fillColor: Colors.white,
        filled: true,
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(10),
        ), ),
  ));

}

0

Thank @Parikshit Chalke for answer. However, setState is quite expensive call if your only want to update your TextFormField and IconButton. Instead, wrap it inside StatefulBuilder and have only child items updated.

Example solution:

import 'package:flutter/material.dart';

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  // initially password is invisible
  bool _passwordVisible = false;
  String _password;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // other widget that does not need update when password visibility is toggled
        Text("I do not require update"),
        
        StatefulBuilder(builder: (_context, _setState) {
          // only following widget gets update when _setState is used
          return TextFormField(
            decoration: InputDecoration(
              suffixIcon: IconButton(
                icon: Icon(
                  _passwordVisible ? Icons.visibility : Icons.visibility_off,
                ),
                onPressed: () {
                  // use _setState that belong to StatefulBuilder
                  _setState(() {
                    _passwordVisible = !_passwordVisible;
                  });
                },
              ),
              labelText: 'Password',
              icon: const Padding(
                padding: const EdgeInsets.only(top: 15.0),
                child: const Icon(Icons.lock),
              ),
            ),
            validator: (val) => val.length < 6 ? 'Password too short.' : null,
            onSaved: (val) => _password = val,
            obscureText: true,
          );
        }),
      ],
    );
  }
}

bikram
  • 7,127
  • 2
  • 51
  • 63
0

I have more useful solution. You can use Provider and listen TextFormField with Consumer Widget

obscure_text_state.dart

import 'package:flutter/material.dart';

class ObscureTextState with ChangeNotifier {
  bool _isTrue = true;
  bool get isTrue => _isTrue;

  get switchObsIcon {
    return _isTrue ? Icon(Icons.visibility_off) : Icon(Icons.visibility);
  }

  void toggleObs() {
    _isTrue = !_isTrue;
    notifyListeners();
  }
}

Then you should listen that state with Consumer where TextFromField is.

            Consumer<ObscureTextState>(
              builder: (context, obs, child) {
                return TextFormField(
                  controller: _passwordController,
                  validator: (value) {
                    if (value.isEmpty) {
                      return "Alan boş bırakılamaz!";
                    } else if (value.length < 6) {
                      return "Şifre en az 6 haneden oluşmalıdır.";
                    } else {
                      return null;
                    }
                  },
                  obscureText:
                      Provider.of<ObscureTextState>(context, listen: false)
                          .isTrue,
                  decoration: InputDecoration(
                      prefixIcon: Icon(Icons.lock),
                      suffixIcon: IconButton(
                        onPressed: () {
                          Provider.of<ObscureTextState>(context, listen: false)
                              .toggleObs();
                        },
                        icon: Provider.of<ObscureTextState>(context,
                                listen: false)
                            .switchObsIcon,
                      ),
                      hintText: "Şifre",
                      border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(20.0))),
                );
              },
            ),
pirekare
  • 151
  • 2
  • 10
-1
    TextFormFeild(
    decoration:InputDecoration(
     icon: _isSecurityIcon == true
  ? IconButton(
   icon: Icon(Icons.visibility_off_outlined),
onPressed: () {
   setState(() {
_isSecurityIcon = false;
   });
  },
)
: IconButton(
icon: Icon(Icons.visibility_outlined),
onPressed: () {
setState(
() {
_isSecurityIcon = true;
    },
    );
    },
   ),
   ),
  );```