I just start to learn flutter, and I'm curious about how can i setState() dynamically in flutter
in React Native i usually used a function like this to setState dynamically:
class foo extends React.Component{
state={
username:null,
password:null
}
toggleSetState(whatState, value){
this.setState({ [whatState]: value })
}
render() {
return (
<View>
<TextInput
value={this.state.username}
onChangeText={(text)=>{toggleSetState(username, text)}
/>
<TextInput
value={this.state.password}
onChangeText={(text)=>{toggleSetState(password, text)}
/>
</View>
);
}
}
what is an equivalent of above code in Flutter?
I've tried this in Flutter but it seems doesn't work
class _LoginFormState extends State<LoginForm> {
String username, password;
void ToogleState(typedata, text){
setState(() {
typedata = text;
});
}
//Widget
TextField(
onChanged: (text){ToogleState(username, text); print(username);},
decoration: InputDecoration(
hintText: 'input username', labelText: 'Username'
),
),
}
after some research and trying, i can achieve what i want with the code below:
class _LoginFormState extends State<LoginForm> {
String username, password;
//create an object
var loginForm = {};
final myController = TextEditingController();
void ToogleState(typedata, text){
setState(() {
//i can assign any different variable with this code
loginForm[typedata] = text;
//output of LoginForm: {username: foo, password: bar}
});
}
//widget
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
onEditingComplete: (){print(loginForm);},
onChanged: (text){ToogleState("username", text); print(loginForm['username']);},
decoration: InputDecoration(
hintText: 'input username', labelText: 'Username'
),
),
TextField(
onEditingComplete: (){print(loginForm);},
onChanged: (text){ToogleState("password", text); print(loginForm['password']);},
decoration: InputDecoration(
hintText: 'input password', labelText: 'Password'
),
),
],
),
);
}
You just need to make a variable to hold the value. I am confused why you are calling setState when you are not modifying ephemeral state
Here are some helpful docs
https://flutter.dev/docs/development/data-and-backend/state-mgmt/ephemeral-vs-app
class _LoginFormState extends State<LoginForm> {
String _username = "";
String __password = "";
void ToogleState( text){
setState(() {
_username = text;
});
}
//Widget
TextField(
onChanged: (text){ToogleState( text); print(username);},
decoration: InputDecoration(
hintText: 'input username', labelText: 'Username'
),
),
}
Related
I am doing some charge information with shared preference but in iOS is not working while in Android is working as expected
#override
void initState() {
super.initState();
readData();
}
I have 2 textfields for fill it up when readData() have data
final email = TextFormField(
keyboardType: TextInputType.emailAddress,
autofocus: false,
initialValue: _email,
validator: (input) {
if(input.isEmpty) {
return 'Introduce un Email';
}
},
onSaved: (input) => _email = input,
decoration: InputDecoration(
hintText: 'Email',
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(32.0))),
);
final password = TextFormField(
autofocus: false,
obscureText: true,
initialValue: _password,
validator: (input) {
if(input.isEmpty) {
return 'Introduce la contraseƱa';
}
},
onSaved: (input) => _password = input,
decoration: InputDecoration(
hintText: 'ContraseƱa',
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(32.0))),
);
readData() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_email = prefs.getString('email');
_password = prefs.getString('password');
});
}
In android works well but not in iOS
I think the right way to update the value of the textfields is to use a TextEditingController.
Your code would look like this:
In your state
final TextEditingController emailCtrl = TextEditingController();
final TextEditingController passwordCtrl = TextEditingController();
readData() async {
final prefs = await SharedPreferences.getInstance();
emailCtrl.text = prefs.getString('email');
password.text = prefs.getString('password');
}
Your TextFields
final email = TextFormField(
// ... your other arguments
controller: emailCtrl
);
final password = TextFormField(
// ... your other arguments
controller: passwordCtrl
);
I'm trying to create an app with block pattern and flutter_block library. It is working, but now I want to reduce the code.
I have a lot of:
dart
Padding(
padding:
EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
child: TextField(
inputFormatters: [
BlacklistingTextInputFormatter(RegExp("[a-zA-Z]"))
],
decoration: InputDecoration(
labelText: 'label text'),
keyboardType: TextInputType.number,
controller: _service,
onChanged: (value) =>
{_prefsBloc.dispatch(SetServicePrefs(value))},
),
),
I'm transforming that in a widget:
dart
class SettingTextField extends StatelessWidget {
final String text;
final String labelText;
SettingTextField({this.text, this.labelText});
#override
Widget build(BuildContext context) {
final PrefsBloc _prefsBloc = BlocProvider.of<PrefsBloc>(context);
final TextEditingController _controller = TextEditingController();
_controller.text = this.text;
if (this.text != null) {
_controller.selection = TextSelection.collapsed(offset: this.text.length);
}
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
child: TextField(
inputFormatters: [BlacklistingTextInputFormatter(RegExp("[a-zA-Z]"))],
decoration: InputDecoration(labelText: this.labelText),
keyboardType: TextInputType.number,
controller: _controller,
onChanged: (value) {}
);
}
}
For each field, I have a different event to dispatch to block like:
_prefsBloc.dispatch(SetServicePrefs(value))
But I really don't know how to pass the type SetServicePrefs to my widget and use it in the onChanged function.
How do I solve this problem?
You can add additional callback field to SettingTextField:
class SettingTextField extends StatelessWidget {
final String text;
final String labelText;
final Function(PrefsBloc bloc, value) onChanged;
...
TextField(
inputFormatters: [BlacklistingTextInputFormatter(RegExp("[a-zA-Z]"))],
decoration: InputDecoration(labelText: this.labelText),
keyboardType: TextInputType.number,
controller: _controller,
onChanged: (value) => onChanged(_prefsBloc, value)
)
...
}
Then call SettingTextField:
SettingTextField(
...
onChanged: (bloc, value) => bloc.dispatch(SomeEventType(value))
...
)
I am working on Flutter TextField widget. I want to show an error message below the TextField widget if the user does not fill that TextField. I only have to use TextField Widget not TextFormField in this case.
A Minimal Example of what you Want:
class MyHomePage extends StatefulWidget {
#override
MyHomePageState createState() {
return new MyHomePageState();
}
}
class MyHomePageState extends State<MyHomePage> {
final _text = TextEditingController();
bool _validate = false;
#override
void dispose() {
_text.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TextField Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Error Showed if Field is Empty on Submit button Pressed'),
TextField(
controller: _text,
decoration: InputDecoration(
labelText: 'Enter the Value',
errorText: _validate ? 'Value Can\'t Be Empty' : null,
),
),
RaisedButton(
onPressed: () {
setState(() {
_text.text.isEmpty ? _validate = true : _validate = false;
});
},
child: Text('Submit'),
textColor: Colors.white,
color: Colors.blueAccent,
)
],
),
),
);
}
}
Flutter handles error text itself, so we don't require to use variable _validate. It will check at runtime whether you satisfied the condition or not.
final confirmPassword = TextFormField(
controller: widget.confirmPasswordController,
obscureText: true,
decoration: InputDecoration(
prefixIcon: Icon(Icons.lock_open, color: Colors.grey),
hintText: 'Confirm Password',
errorText: validatePassword(widget.confirmPasswordController.text),
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
),
);
String validatePassword(String value) {
if (!(value.length > 5) && value.isNotEmpty) {
return "Password should contain more than 5 characters";
}
return null;
}
Note: User must add at least one character to get this error message.
I would consider using a TextFormField with a validator.
Example:
class MyHomePageState extends State<MyHomePage> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TextFormField validator'),
),
body: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
decoration: InputDecoration(
hintText: 'Enter text',
),
textAlign: TextAlign.center,
validator: (text) {
if (text == null || text.isEmpty) {
return 'Text is empty';
}
return null;
},
),
RaisedButton(
onPressed: () {
if (_formKey.currentState.validate()) {
// TODO submit
}
},
child: Text('Submit'),
)
],
),
),
);
}
}
If you use TextFormField then you could easily implement 'Error
below your text fields'.
You can do this without using _validate or any other flags.
In this example, I have used validator method of TextFormField
widget. This makes the work a lot more easier and readable at the
same time.
I also used FormState to make the work more easier
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final _form = GlobalKey<FormState>(); //for storing form state.
//saving form after validation
void _saveForm() {
final isValid = _form.currentState.validate();
if (!isValid) {
return;
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Form(
key: _form, //assigning key to form
child: ListView(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Full Name'),
validator: (text) {
if (!(text.length > 5) && text.isNotEmpty) {
return "Enter valid name of more then 5 characters!";
}
return null;
},
),
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
validator: (text) {
if (!(text.contains('#')) && text.isNotEmpty) {
return "Enter a valid email address!";
}
return null;
},
),
RaisedButton(
child: Text('Submit'),
onPressed: () => _saveForm(),
)
],
),
),
),
);
}
}
I hope this helps!
For TextFiled and TextFormFiled Validation you can use this Example I hope this will helpful for you people.
TextField(
enableInteractiveSelection: true,
autocorrect: false,
enableSuggestions: false,
toolbarOptions: ToolbarOptions(
copy: false,
paste: false,
cut: false,
selectAll: false,
),
controller: _currentPasswordController,
obscureText: passwordVisible,
decoration: InputDecoration(
errorText: Validators.password(
_currentPasswordController.text),
filled: true,
fillColor: Colors.white,
contentPadding:
const EdgeInsets.fromLTRB(20, 24, 12, 16),
border: const OutlineInputBorder(
borderRadius:
BorderRadius.all(Radius.circular(8.0))),
// filled: true,
labelText: 'Password',
hintText: 'Enter your password',
suffixIcon: GestureDetector(
onTap: () {
setState(() {
passwordVisible = !passwordVisible;
});
},
child: Container(
margin: const EdgeInsets.all(13),
child: Icon(
passwordVisible
? FontAwesomeIcons.eyeSlash
: Icons.remove_red_eye_sharp,
color: ColorUtils.primaryGrey,
size: 25)),
),
),
),
Validation Message Example Code
static password(String? txt) {
if (txt == null || txt.isEmpty) {
return "Invalid password!";
}
if (txt.length < 8) {
return "Password must has 8 characters";
}
if (!txt.contains(RegExp(r'[A-Z]'))) {
return "Password must has uppercase";
}
if (!txt.contains(RegExp(r'[0-9]'))) {
return "Password must has digits";
}
if (!txt.contains(RegExp(r'[a-z]'))) {
return "Password must has lowercase";
}
if (!txt.contains(RegExp(r'[#?!#$%^&*-]'))) {
return "Password must has special characters";
} else
return;
}
I have a weird problem in flutter TextFormField. I implemented form validation in TextFormField. But onSaved() function doesn't get called after successful validation.
First I created basic Widget using TextFormField
--- In AppWidgets class ---
static Widget buildTextFormField(
String labelText,
String helperText,
IconData prefixIcon, {
Widget suffixIcon,
bool obscureText = false,
TextInputType keyboardType = TextInputType.text,
TextInputAction textInputAction = TextInputAction.none,
FocusNode focusNode,
ValueChanged<String> onFieldSubmitted,
TextEditingController controller,
FormFieldValidator<String> validator,
FormFieldSetter<String> onSaved,
bool isLightTheme = false,
}) {
return Theme(
data: isLightTheme
? AppThemesLight.textFormFieldThemeData
: AppThemesDark.textFormFieldThemeData,
child: TextFormField(
controller: controller,
validator: validator,
onSaved: onSaved,
keyboardType: keyboardType,
textInputAction: textInputAction,
focusNode: focusNode,
onFieldSubmitted: onFieldSubmitted,
obscureText: obscureText,
decoration: InputDecoration(
filled: true,
fillColor: isLightTheme
? AppColorsLight.textFieldFillColor
: AppColorsDark.textFieldFillColor,
labelText: labelText,
helperText: helperText,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(AppDimensions.textFieldBorderRadius),
),
),
prefixIcon: Icon(
prefixIcon,
color: isLightTheme
? AppColorsLight.primaryTextColor
: AppColorsDark.primaryTextColor,
),
suffixIcon: suffixIcon,
),
),
);
}
From that, created email text from field.
static Widget buildEmailTextFormField(LoginState loginState) {
return AppWidgets.buildTextFormField(
'Email address',
'Your email address',
Icons.email,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
focusNode: loginState.focusNodes[0],
onFieldSubmitted: (String value) {
print('submitted $value');
loginState.onFocusChanged(index: 0);
},
validator: (String email) {
print('validator $email');
return InputValidators.validateEmail(email);
},
onSaved: (String email) {
print('saved $email');
loginState.email = email;
},
);
}
Here is the email validator I used.
static String validateEmail(String email) {
Pattern pattern =
r'^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regex = new RegExp(pattern);
if (email.isEmpty)
return 'Email can\'t be empty';
else if (!regex.hasMatch(email))
return 'Enter valid email address';
else
return null;
}
I tested above code by putting print statement inside onSaved() function, but it's not printing after successful validation.
The onSaved() function won't be called automatically after successful validation. We have to call _formKey.currentState.save() manually to save our variables.
Form(
key: key,
child: TextFormField(
onSaved: (val) {
print('saved');
},
validator: (val) {
print('validating');
},
),
),
RaisedButton(
child: Text('Click me'),
onPressed: () {
if (key.currentState.validate()) {
key.currentState.save();
print('valid');
}
},
),
Did you call this method formKey.currentState.save() ?
in my case i forgot to call this after adding this it has worked.
Hai i am new in flutter/dart and hopefully you guys can help me on this. I am having this issue when i use obscureText: true and validator: in a TextFormField somehow i am unable to type anything in that field. Can someone tell me why is this?
class _LoginPageState extends State<LoginPage>{
final formKey = new GlobalKey<FormState>();
String _email;
String _password;
void validateAndSave(){
final form = formKey.currentState;
if (form.validate()){
print('Form is valid');
}else{
print('Form is invalid');
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Login'),
),
body: new Container(
padding: const EdgeInsets.all(20.0),
child: new Form(
key: formKey,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new TextFormField(
decoration: new InputDecoration(labelText: 'Email'),
validator: (value) => value.isEmpty ? 'Email can\'t be empty' : null,
onSaved: (value) => _email = value,
),
new TextFormField(
decoration: new InputDecoration(labelText: 'Password'),
obscureText: true,
validator: (value) => value.isEmpty ? 'Password can\'t be empty' : null,
onSaved: (value) => _password = value,
),
new RaisedButton(
child: new Text('Login', style: new TextStyle(fontSize: 20.0)),
onPressed: validateAndSave,
)
],
),
)
)
);
}
}
There is nothing wrong with the above code.
Anyway as I was testing the above code so Added/replaced few things like a validator class FieldValidator and instead of column use ListView etc.
Check out the code :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: LoginPage(),
);
}
}
class FieldValidator {
static String validateEmail(String value) {
if (value.isEmpty) return 'Email can\'t be empty!';
Pattern pattern =
r'^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regex = RegExp(pattern);
if (!regex.hasMatch(value)) {
return 'Enter Valid Email!';
}
return null;
}
static String validatePassword(String value) {
if (value.isEmpty) return 'Password can\'t be empty!';
if (value.length < 7) {
return 'Password must be more than 6 charater';
}
return null;
}
}
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
String _email;
String _password;
final _formKey = GlobalKey<FormState>();
void validateAndSave() {
final form = _formKey.currentState;
if (form.validate()) {
form.save();
print('Form is valid $_email $_password');
} else {
print('Form is invalid');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login'),
),
body: Container(
padding: const EdgeInsets.all(20.0),
child: Form(
key: _formKey,
child: ListView(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
validator: FieldValidator.validateEmail,
onSaved: (value) => _email = value.trim(),
),
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
validator: FieldValidator.validatePassword,
onSaved: (value) => _password = value.trim(),
),
RaisedButton(
child: Text('Login', style: TextStyle(fontSize: 20.0)),
onPressed: validateAndSave,
)
],
),
),
),
);
}
}
Hope it helps !
Assigning obscure text from a variable works for me.
obscureText: _obscurePasswordText