How can I disable a button when the user hasn't added any content to the textfield yet and then enable it once the user type something?
Is it possible? And if no is there another way to do it?
Thanks
return AlertDialog(
contentPadding: EdgeInsets.only(top: 5.0),
content: Padding(
padding: EdgeInsets.only(left: 15.0, right: 15.0, bottom: 20.0),
child: TextField(
keyboardType: TextInputType.multiline,
decoration: InputDecoration(labelText: 'Send a new message...'),
onChanged: (value) {
setState(() {
_newMessage = value;
});
},
),
),
actions: [
IconButton(
color: Colors.tealAccent,
icon: Icon(
Icons.navigate_next_rounded,
size: 40.0,
),
onPressed: _newMessage.trim().isEmpty ? null : _sendNewMessage,
),
],
);
To achive this you have to get the lenght of the input from the textfield, the correct way is to use a TextEditingController but for this simple purpose a workarount should do the job.
Code:
Initialize a new bool isInputEmpty before the return AlertDialog
onChanged: (value) {
setState(() {
_newMessage = value;
if(_newMessage.length > 0){ //add these lines
isInputEmpty = false;
} else {
isInputEmpty = true;
}
});
},
and to disable to button you can wrap him inside a IgnorePointer
IgnorePointer(
ignoring: isInputEmpty,
child: IconButton(...),
),
You can even change the button color:
IconButton(
color: isInputEmpty ? Colors.grey : Colors.tealAccent,
),
Use a Text Editing Controller https://flutter.dev/docs/cookbook/forms/text-field-changes
final controller = TextEditingController();
onPressed: controller.text.length==0 ? null : _sendNewMessage,
pls check if the .length is correct can't remember haha
Related
I am trying to create an overlay widget for TextFormField suffix Icon. Normally we would be using ToolTip, but just trying something new because the overlay widget can be customized. I want to change the suffix Icon Color of TextFormField if it is not validated from Grey to Red. So when the Icon becomes red it alerts the user that something is wrong, when the user clicks on it overlay widget will be shown.
My Code for OverLay widget.
void _showOverlay(BuildContext context) async {
OverlayState? overlayState = Overlay.of(context);
OverlayEntry overlayEntry;
overlayEntry = OverlayEntry(builder: (context) {
return Positioned(
left: MediaQuery.of(context).size.width * 0.1,
top: MediaQuery.of(context).size.height * 0.23,
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Material(
child: Container(
alignment: Alignment.center,
color: Colors.grey.shade200,
padding:
EdgeInsets.all(MediaQuery.of(context).size.height * 0.02),
width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.height * 0.06,
child: const Text(
'Name should be more than 2 characters',
style: TextStyle(color: Colors.black),
),
),
),
),
);
});
overlayState!.insert(overlayEntry);
await Future.delayed(const Duration(seconds: 3));
overlayEntry.remove();
}
My Submit Button method:
void _submitForm() {
setState(() {
_autoValidateMode = AutovalidateMode.always;
});
final form = _formKey.currentState;
if (form == null || !form.validate()) return;
form.save();
print(_name);
}
My TextFormField widget:
TextFormField(
controller: nameController,
keyboardType: TextInputType.name,
textInputAction: TextInputAction.next,
textCapitalization: TextCapitalization.words,
validator: (String? value) {
if (value == null || value.trim().isEmpty) {
return;
}
return null;
},
onSaved: (String? value) {
_name = value;
},
decoration: kTextInputDecoration.copyWith(
labelText: 'Full Name',
prefixIcon: const Icon(Icons.person),
suffixIcon: IconButton(
padding: EdgeInsets.zero,
onPressed: () {
_showOverlay(context);
},
icon: const Icon(
Icons.info,
color: Colors.grey //change icon color according to form validation
))),
My submit button.
ElevatedButton(
onPressed: () {
_submitForm();
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(10)),
child: const Text(
'Submit',
style: TextStyle(fontSize: 20),
)),
I want to change the color of the suffix icon color when the submit button is pressed. If the form is not validated the color should change to red or the default is grey. Thank you very much in advance for your help.
Warning: the below solution is currently broken on the stable channel 3.3.9 but it works fine in beta.
The issue was marked as fixed on August 23, 2022 so I have good hope that we will get it in stable soon.
You can:
create a class which extends MaterialStateColor
create an instance in your build method based on your theme
pass the instance to suffixIconColor
class InfoIconColor extends MaterialStateColor {
const InfoIconColor(
super.defaultColor, {
required this.disabledColor,
required this.errorColor,
});
final Color disabledColor;
final Color errorColor;
#override
Color resolve(Set<MaterialState> states) {
if (states.contains(MaterialState.error)) return errorColor;
if (states.contains(MaterialState.disabled)) return disabledColor;
return Color(super.value);
}
}
TextFormField(
// ...
decoration: kTextInputDecoration.copyWith(
labelText: 'Full Name',
suffixIcon: // ...
suffixIconColor: // ...
),
)
In the meantime, you can can use the same InfoIconColor in the theme of your app (or wrap your widget with a theme widget) like so:
final theme = Theme.of(context);
Theme(
data: theme.copyWith(
inputDecorationTheme: theme.inputDecorationTheme.copyWith(
suffixIconColor: //...
),
),
child: //...
)
You can create bool to store the validation or directly use the validate method.
///state level
bool _isValidate = true;
......
/// inside TextFormField
validator: (String? value) {
if (value == null || value.trim().isEmpty) {
setState(() {
_isValidate = false;
});
return "Error message";
}
return null;
},
onTap: () {
setState(() {
_isValidate = true;
});
},
/// and Icon color
icon: Icon(
Icons.info,
color: _isValidate ? Colors.grey : Colors.red,
),
I want to implement a function as following:
There is a button named 自定义. In this page, there is a variable named money. When I click the button, an AlertDialog with a TextFormField inside will occur. I hope that after inputting a number X into the TextFormField and clicking button ok to exit the AlertDialog, money would be changed to X. I have used onSaved to save the variable, and used _formkey.currentState.save(), but money didn't change. What's wrong with my codes? Here are my codes:
void _showMessageDialog() {
//int addMoney = 0;
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
key: _formKey,
title: new Text('INPUT'),
content: TextFormField(
maxLines: 1,
keyboardType: TextInputType.emailAddress,
autofocus: false,
style: TextStyle(fontSize: 15),
decoration: new InputDecoration(
border: InputBorder.none,
hintText: 'input',
),
onSaved: (value) {
money = int.parse(value?.trim() ?? '0') as double;
print(money);
}
),
actions: <Widget>[
new TextButton(
key: _formKey,
child: new Text("ok"),
onPressed: () {
_formKey.currentState?.save();
Navigator.of(context).pop();
},
),
],
);
},
);
}
Here are the codes relative to the button 自定义
OutlinedButton(
style: OutlinedButton.styleFrom(
side: BorderSide(
width: 1,
color: Colors.blueAccent
)
),
onPressed: () {
// Navigator.of(context).push(
// _showMessageDialog()
// );
_showMessageDialog();
},
child: Text(
"自定义",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: Colors.blueAccent
),
),
),
I know maybe I have made a big mistake, but it is my first Flutter project. Thanks for your advices.
I would use ValueNotifier for this. But first you need to add a controller to your TextFormField so you can get the text user typed in.
//initialize it
final myController = TextEditingController();
TextFormField(
controller: myController, //pass it to the TextFormField
),
TextButton(
child: new Text("ok"),
onPressed: () {
String input = myController.text; //this is how you get the text input
_formKey.currentState?.save();
Navigator.of(context).pop();
},
),
As I said you also need to initialize ValueNotifier and pass it to _showMessageDialog
ValueNotifier<int> money = ValueNotifier(0);
_showMessageDialog(money); //and pass it to your function
void _showMessageDialog(ValueNotifier<int> money) {
TextButton(
child: new Text("ok"),
onPressed: () {
String input = myController.text; //this is how you get the text input
money.value = int.parse(input); //and then update your money variable
_formKey.currentState?.save();
Navigator.of(context).pop();
},
),
}
I m new to flutter, need help to set the value of the DropdownButton programmatically.
The value is from textfield. Once i click it, it will set the value at the dropdownbutton automatically.
Widget _districtListContainer() {
return Container(
width: 360.0,
child: new InputDecorator(
decoration: InputDecoration(
suffixIcon: new Icon(
Icons.search,
color: Colors.blue[700],
),
labelText: 'Select District',
labelStyle: TextStyle(fontSize: 12.0)),
isEmpty: _selectedDistrict == null,
child: new DropdownButtonHideUnderline(
child: new DropdownButton<District>(
value: _selectedDistrict,
isDense: true,
isExpanded: false,
onChanged: (District newValue) {
setState(() {
_selectedDistrict = newValue;
});
},
items: _listDistrict?.map((District value) {
return new DropdownMenuItem<District>(
value: value,
child: new Text(
value.district != null ? value.district : '',
style: new TextStyle(fontSize: 11.0),
),
);
})?.toList() ??
[],
),
),
),
margin: EdgeInsets.only(bottom: 10.0));
}
thanks
First Of All, Add the data into the list[] From the TextFormfield then retrieve the list into DropDownButton item.
Also, Make Sure, DropDown Button List Display Textformfield data insert activity could not be able to update simultaneously.
I have a DropDownButtonFormField I need to check this. If the dropdown value is not selected by the user then assign the initial value when submitting.
Custom DropDown
Container myDropDownContainer(String initialVal, List<String> listItems,
String text, Function myFunc, Function validate) {
return Container(
margin: const EdgeInsets.all(8),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
width: 120,
child: Text(
text,
style: kTextStyle,
),
),
const SizedBox(
width: 20,
),
Expanded(
child: Container(
height: 50,
decoration: BoxDecoration(
color: Colors.orangeAccent,
borderRadius: BorderRadius.circular(5)),
child: DropdownButtonFormField<String>(
autovalidateMode: AutovalidateMode.always,
//menuMaxHeight: 300,
validator: (value) {
if(value!.isEmpty) {
return "485s4a8sd4as85";
}
} ,
decoration: const InputDecoration(border: InputBorder.none),
isExpanded: true,
onTap: () => myFunc,
//borderRadius: BorderRadius.circular(5),
value: initialVal,
icon: const Icon(
Icons.arrow_downward,
color: Colors.black38,
),
iconSize: 24,
elevation: 16,
dropdownColor: Colors.deepOrange,
style: kTextStyle.copyWith(color: Colors.black),
onChanged: (val) => myFunc(val),
items: listItems.map<DropdownMenuItem<String>>((String? val) {
return DropdownMenuItem(
//TODO: Set default values
value: val,
child: Text(
val,
style: kTextStyle.copyWith(color: Colors.black),
),
);
}).toList(),
),
),
)
],
),
);
}
This is my onChanged property that assigns the selected value by the user. I added some explanations about what I am trying to do.
String _valueCinsiyet = "Diğer"; // initial value
void onChangedCinsiyet(String? newVal) {
setState(() {
if(newVal==null) {
_formData.setCinsiyet(_valueCinsiyet);
/*
'if newVal is null' means that if the value is not selected by the user
then set the initialValue( _valueCinsiyet)
*/
} else {
/*
if newVal is not null then assign the newVal( which means the selected value)
into my initialValue, then set the data to use it on different pages. What is missing?
*/
_valueCinsiyet = newVal;
_formData.setCinsiyet(_valueCinsiyet);
}
});
}
You can use nullable data to track DropdownButtonFormField changes. Being nullable you can check if it is null or not, no need to anything extra on onChanged: just assign new value usual way.
On state before build method: String? value; // value to keep track
child: DropdownButtonFormField<String>(
value: value,
onChanged: (val) {
setState(() {
value = val;
});
},
Now onSaved/submit button you can pass value by checking null, simple way is
value?? "default Value". In your case, it is value??Diğer
Creating a Screen where I want to perform Flutter-FireBase Searching.But Visibility Toggle is not working as desired.
Desired Toggle Behaviour : When clicked on TextForm field , prefix icon and result card should be visible. Upon Clicking the Prefix Icon(Back Arrow) , Result List (Card) and the prefix icon itself should become invisible and TextField should unfocus .
Actual Behaviour : On clicking the prefix icon , Result set and prefix icon don't disappear , Prefix icon remains there and result set becomes invisible but occupies some space beneath the TextFormField
class AddAppointmentWidget extends StatefulWidget {
#override
_AddAppointmentWidgetState createState() => _AddAppointmentWidgetState();
}
class _AddAppointmentWidgetState extends State<AddAppointmentWidget> {
bool searchbartapped = false;
var queryResultSet = [];
var tempSearchStore = [];
// Search Function
initiateSearch(value) {
//body
}
#override
Widget build(BuildContext context) {
return ListView(
children: [
SizedBox(
height: 15,
),
Padding(
padding: const EdgeInsets.all(18.0),
child: Text('Search',
style: TextStyle(fontSize: 35, fontWeight: FontWeight.bold)),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
flex: 5,
child: TextFormField(
style: TextStyle(color: Color(0xff2a2a2a), fontSize: 18),
keyboardType: TextInputType.name,
onChanged: (value) {
initiateSearch(value);
},
onTap: () {
setState(() {
searchbartapped = true;
});
},
cursorColor: Color(0xff2a2a2a),
cursorWidth: 1.5,
decoration: InputDecoration(
hintText: "Search by Name",
prefixIcon: Visibility(
visible: searchbartapped,
child: IconButton(
icon: Icon(Icons.arrow_back),
color: Colors.black54,
onPressed: () {
setState(() {
searchbartapped = !searchbartapped;
queryResultSet = [];
tempSearchStore = [];
});
FocusScope.of(context).unfocus();
}),
),
)),
),
],
),
),
Visibility(
visible: searchbartapped,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
padding: EdgeInsets.all(5.0),
primary: false,
shrinkWrap: true,
children: tempSearchStore.map((element) {
print(element['name']);
return buildResult(context, element);
}).toList()),
),
),
],
);
}
}
Note The buildResult widget is working perfectly fine.
Problem is only with the visibilty toggle
The issue: When you tap the prefixIcon:
onPressed is called, setting searchbartapped to false which is what you want.
The onTap method of your TextFormField is also called (since prefixIcon is inside it), setting searchbartapped to true.
So what you want is to prevent the second event from happening. I tried to prevent the notification from bubbling up the tree but I couldn't. So what I ended up doing is a bit more manual but works just as well.
Solution: Add a variable (for example hideSearchTapped) which is set to true when the prefixIcon is called. Then when the onTap method of your TextFormField is called, check this variable:
If hideSearchTapped is true, set it to false
Else change searchbartapped as you did
Here is a working example:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() async {
runApp(
MaterialApp(
home: Scaffold(
body: new AddAppointmentWidget(),
),
),
);
}
class AddAppointmentWidget extends StatefulWidget {
#override
_AddAppointmentWidgetState createState() => _AddAppointmentWidgetState();
}
class _AddAppointmentWidgetState extends State<AddAppointmentWidget> {
bool searchbartapped = false;
bool hideSearchTapped = false;
var queryResultSet = [];
var tempSearchStore = [];
// Search Function
initiateSearch(value) {
//body
}
#override
Widget build(BuildContext context) {
return ListView(
children: [
SizedBox(
height: 15,
),
Padding(
padding: const EdgeInsets.all(18.0),
child: Text('Search', style: TextStyle(fontSize: 35, fontWeight: FontWeight.bold)),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
flex: 5,
child: TextFormField(
style: TextStyle(color: Color(0xff2a2a2a), fontSize: 18),
keyboardType: TextInputType.name,
onChanged: (value) {
initiateSearch(value);
},
onTap: () {
setState(() {
if (hideSearchTapped) {
hideSearchTapped = false;
} else {
searchbartapped = true;
}
});
},
cursorColor: Color(0xff2a2a2a),
cursorWidth: 1.5,
decoration: InputDecoration(
hintText: "Search by Name",
prefixIcon: Visibility(
visible: searchbartapped,
child: IconButton(
icon: Icon(Icons.arrow_back),
color: Colors.black54,
onPressed: () {
hideSearchTapped = true;
searchbartapped = !searchbartapped;
queryResultSet = [];
tempSearchStore = [];
setState(() {
});
FocusScope.of(context).unfocus();
return true;
}),
),
)),
),
],
),
),
Visibility(
visible: searchbartapped,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
padding: EdgeInsets.all(5.0),
primary: false,
shrinkWrap: true,
children: tempSearchStore.map((element) {
print(element['name']);
}).toList()),
),
),
],
);
}
}
Note: you should use lowerCamelCase to name your variable. So searchbartapped would become searchBarTapped.