I can't change the hinttext in the dropdownButton - flutter

This is my code, and i dont know why the hint doesnt change, when i click the options, obviously all this code is inside a class, I tried to use a for loop to identify the list positions , and if they were right, it would appear on the screen.
final listgenre = ["Masc", "Fem", "Não Binário"];
var listgenre_value;
String newValue = "";
var valueChoose;
String hintValue = "";
Padding(
padding: EdgeInsets.all(16),
child: Container(
padding: EdgeInsets.only(left: 16, top: 10, right: 15),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 2),
borderRadius: BorderRadius.circular(20)),
child: DropdownButton<String>(
hint: Text("Genero :$hintValue"),
dropdownColor: Colors.white,
icon: Icon(Icons.arrow_drop_down),
iconSize: 36,
iconEnabledColor: Colors.black,
isExpanded: true,
style: TextStyle(fontSize: 17, color: Colors.black),
value: valueChoose,
underline: SizedBox(
width: 320,
height: 200,
),
items: listgenre.map((valueitem) {
return DropdownMenuItem(
value: valueitem,
child: Text(valueitem),
);
}).toList(),
onChanged: (newValue) {
setState(() {
for (int i = 0; i <= listgenre.length; i++) {
if (listgenre[i] != newValue) {
listgenre_value = i + 1;
} else {
hintValue = "$newValue";
// ignore: void_checks
return listgenre_value;
}
}
Object? valueChoose = newValue;
String valueChoosen = valueChoose.toString();
});
},
))),

As per your comments, the same code doesn't give the expected output.
It is only possible if you are also initialising the variables inside the build method, which shouldn't be the case.
Incorrect Code
#override
Widget build(BuildContext context) {
// ---- your variables INSIDE build method
final listgenre = ["Masc", "Fem", "Não Binário"];
var listgenre_value;
String newValue = "";
var valueChoose;
String hintValue = "";
// ------- rest of the code continued
Correct Code
final listgenre = ["Masc", "Fem", "Não Binário"];
var listgenre_value;
String newValue = "";
var valueChoose;
String hintValue = "";
#override
Widget build(BuildContext context) {
// ------- rest of the code continued
As the build method is called every time setState is called, hintValue was being initialised to "" i.e empty string.

The correct way to fix this error is abdev's comment. It will help if you put the variables used by the DropdownButton widget above the build method.
I hope it was helpful!

Related

TRYING TO GET interger from TexteditingController to build a M

i want to make a dodging (Multiplication) Table...and wanna take input from user from TexteditingController, but i am not able to take the integer value from the Text Editing controller. looking forward if someone would look into it,
TextEditingController num = TextEditingController();
void table() {
print('enter the number $num');
for (var i = 1; i <= 10; i++) {
print('$num*$i = ${num * i}'); //<error>(The operator '*' isn't defined for the type,,'')
}
}
Scaffold(
body: Column(
children: [
TextFormField(
controller: TextEditingController(),
),
Container(
height: 400,
width: 400,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(20),
),
child: ElevatedButton(
onPressed: table,
child: Text('')),
),
],
));
}
}
The error comes from the fact that you access the TextEditingController directly instead of grabbing the text it contains.
Correct would be:
TextEditingController controller = TextEditingController();
void table() {
var enteredText = controller.text;
var enteredNumber = int.parse(enteredText)
for (var i = 1; i <= 10; i++) {
print('$enteredNumber*$i = ${enteredNumber * i}');
}
}

Flutter: TextField auto add a dot when input multiple spaces

I just implement a simple TextField, but when I input multiple spaces, it auto add a dot before that.
my-custom-flutter-textfield
Here is my custom TextField widget
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(5),
child: Column(children: [
Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.only(bottom: 5),
child: Text(
title,
),
)),
TextField(
controller: _controller,
autocorrect: false,
decoration: InputDecoration(
isDense: true,
contentPadding: const EdgeInsets.all(10),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(
color: Colors.black,
width: 2,
),
borderRadius: BorderRadius.circular(5),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(
color: Colors.orange,
width: 2,
),
borderRadius: BorderRadius.circular(5),
),
))
]));
}
This is a standard function of the iOS keyboard and most Android keyboards. I don't think you can control that from Flutter.
I don't think that has anything to do with the app itself, but the phone. You'd need to disable that from the phone's settings.
Although, if you really need to be able to type double spaces, here is how I'd implement it.
final TextEditingController controller = TextEditingController();
TextSelection? cursor;
int length = 0;
String lastChar = '';
String currentChar = '';
String replaceCharAt(String oldString, int index, String newChar) {
// function from https://stackoverflow.com/questions/52083836/how-to-replace-only-one-character-in-a-string-in-dart
return oldString.substring(0, index) +
newChar +
oldString.substring(index + 1);
}
void removeDotOnSpace(String input) {
//save the position of the cursor
cursor = controller.selection;
lastChar = currentChar;
// if the input isn't empty and if you're not removing text
if (input.isNotEmpty && input.length > length) {
currentChar = input[input.length - 1];
// if it has at least two characters, the second to last being a dot and the "lastChar" variable not being a dot
if (input.length >= 2 &&
input[input.length - 2] == '.' &&
lastChar != '.') {
// replace the dot and set state. Because setstate resests cursor position we need to save it and give it back.
setState(() {
controller.text = replaceCharAt(input, input.length - 2, ' ');
controller.selection = cursor!;
});
}
} else {
currentChar = '';
lastChar = '';
}
length = input.length;
}
Put this inside a stateful widget and use it inside the onchanged function
TextFormField(
controller: controller,
onChanged: (value) {
removeDotOnSpace(value);
},
),
PS: Unless it's essential for your textfields to be able to have double spaces, you should just let it be.
try textCapitalization: TextCapitalization.words,
put this function onBlur in form inputs:
const handleTrimDataAndRemoveDot = () => {
const trimmedData = formData.trim();
let validatedData;
if (trimmedData.charAt(trimmedData.length - 1) === ".") {
validatedData = trimmedData.replace(
trimmedData.charAt(trimmedData.length - 1),
""
);
} else {
validatedData = trimmedData;
}
setFormData(validatedData);
};

Slider onChangeEnd property doesn't work properly

I want to save to Firestore the last slider value after changes. I tried to do that using onChangeEnd, but instead of last value, I get first picked value.
Examples
What I want to get:
Current slider position is 3. I want to press 3 on slider and slide it to 5 because my answer is 5. I want to be saved in Firestore that my answer is 5. After that, it's not possible to move slider and give new answer.
What I am getting #1:
Current slider position is 3. I press 3 on slider and I want to slide it to 5 because I want my answer to be 5. In Firestore is saved that my answer is 3. After that, it's not possible to move slider and give new answer.
What I am getting #2:
Current slider position is 3. I press 5 on slider (without sliding) and I want my answer to be 5. In Firestore is saved that my answer is 5. After that, it's not possible to move slider and give new answer.
import 'package:flutter/material.dart';
import 'package:gamiforms/services/database.dart';
class LinearScale extends StatefulWidget {
String question, formId;
LinearScale(this.question, this.formId);
#override
LinearScaleState createState() => LinearScaleState(question);
}
class LinearScaleState extends State<LinearScale> {
String question;
LinearScaleState(this.question);
double overall = 3.0;
String overallStatus = "Good";
static final formKey = new GlobalKey<FormState>();
DatabaseService databaseService = DatabaseService();
String answer = "";
bool isLoading = false;
bool enabled = true;
uploadFormData(overall) {
//if (formKey.currentState.validate()) {
setState(() {
isLoading = true;
});
print('overall $overall');
if (overall == 1.0) answer = "Fail";
if (overall == 2.0) answer = "Acceptable";
if (overall == 3.0) answer = "Good";
if (overall == 4.0) answer = "Very good";
if (overall == 5.0) answer = "Excellent";
print('answer $answer');
Map<String, String> answerMap = {
"question": this.question,
"answer": answer,
};
print("${widget.formId}");
databaseService.addAnswerData(answerMap, widget.formId).then((value) {
answer = "";
question = this.question;
setState(() {
isLoading = false;
});
}).catchError((e) {
print(e);
});
}
#override
Widget build(BuildContext context) {
var screenSize = MediaQuery.of(context).size;
var width = screenSize.width;
var height = screenSize.height;
print('TEST $question');
return SizedBox(
width: width,
height: height - 100,
child: Container(
margin: EdgeInsets.only(top: 30.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
margin: EdgeInsets.only(left: 16.0),
child: Text(
question,
style: TextStyle(fontSize: 20),
)),
Container(
margin: EdgeInsets.symmetric(vertical: 50.0),
child: Text(
overallStatus,
style: TextStyle(
color: Colors.teal[800],
fontWeight: FontWeight.bold,
fontSize: 30.0),
textAlign: TextAlign.center,
),
),
Expanded(
child: Center(
child: Slider(
value: overall,
onChanged: (value) {
setState(() {
overall = value.round().toDouble();
_getOverallStatus(overall);
});
},
onChangeEnd: enabled
? (value) {
enabled = false;
setState(() {
overall = value.round().toDouble();
uploadFormData(overall);
});
}
: null,
label: '${overall.toInt()}',
divisions: 4,
min: 1.0,
max: 5.0,
),
),
)
],
),
),
);
}
_getOverallStatus(double overall) {
switch (overall.toInt()) {
case 1:
overallStatus = 'Fail';
break;
case 2:
overallStatus = 'Acceptable';
break;
case 3:
overallStatus = 'Good';
break;
case 4:
overallStatus = 'Very good';
break;
default:
overallStatus = 'Excellent';
break;
}
}
}

Show counter to number of elements hidden when overflow occurs in flutter row widget

Can anyone please help to implement this feature of Gmail that shows the counter to number of emails hidden when the email list becomes large ? I want to implement this in row widget where instead of being scrollable extra elements count is shown when overflow occurs.Gmail shows +15 counter for hidden emails
I was Curious to give a try to achieve the same effect, as asked.
Just in case, If anyone want a start for writing a custom one, then below code may help.
Here is my Code, Feel free to give any suggestions,
(For Now delete button in chips is not working bcoz of some logic problem, I will make it work another day)
import 'package:flutter/material.dart';
class Demo3 extends StatefulWidget {
#override
_Demo3State createState() => _Demo3State();
}
class _Demo3State extends State<Demo3> {
String temp = "";
bool showChips = false;
List<Widget> chipsList = new List();
TextEditingController textEditingController = new TextEditingController();
final _focusNode = FocusNode();
int countChipsToDeleteLater = 0;
#override
void initState() {
super.initState();
_focusNode.addListener(() {
print("Has focus: ${_focusNode.hasFocus}");
if (!_focusNode.hasFocus) {
showChips = false;
setState(() {});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(new FocusNode());
},
child: new Container(
height: 500,
child: new Center(
child: Container(
width: 300,
child: !showChips
? Row(
children: [
buildTextField(),
showNumberWidgetIfAny(),
],
)
: Center(
child: Wrap(
children: [
Wrap(
children: buildChips(),
),
buildTextField(),
],
),
),
),
),
),
),
);
}
buildChips() {
return chipsList;
}
buildTextField() {
return Container(
width: 200,
child: new TextField(
showCursor: true,
focusNode: _focusNode,
autofocus: true,
cursorColor: Colors.black,
style: new TextStyle(fontSize: 22.0, color: Colors.black),
controller: textEditingController,
// decoration: InputDecoration.collapsed(
// hintText: "",
// ),
onChanged: (value) {
if (value.contains(" ")) {
checkWhatToStoreInChips(value, countChipsToDeleteLater);
textEditingController.clear();
setState(() {
showChips = true;
});
countChipsToDeleteLater++;
}
},
),
);
}
checkWhatToStoreInChips(String val, int chipsIndex) {
temp = "";
for (int i = 0; i < val.length; i++) {
if (val[i] == " ") {
break;
}
temp = temp + val[i];
}
addToChips(temp, chipsIndex);
}
addToChips(String tmp, int chipsIndex) {
chipsList.add(Chip(
// onDeleted: () {
// if (chipsList.length == 0) {
// countChipsToDeleteLater = 0;
// }
// chipsList.removeAt(chipsIndex);
// print(chipsList.length);
// print(chipsIndex);
// setState(() {});
// },
avatar: CircleAvatar(
backgroundColor: Colors.grey.shade800,
child: Text(tmp[0]),
),
label: Text(temp),
));
}
showNumberWidgetIfAny() {
int len = chipsList.length;
if (len >= 1) {
return GestureDetector(
onTap: () {
showChips = true;
setState(() {});
},
child: new Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.blue,
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: new Text(
"${chipsList.length.toString()} ",
style: new TextStyle(color: Colors.white, fontSize: 22),
),
),
),
);
}
return Container();
}
}
How it works:
Write something in text field, then press space, showChips boolean will become true
onChanged will detect the space and will send the string to a function.
That function will extract the string before space and then will add the string to a chip,
Finally the chip will be added to a chipslist.
We will have a boolean variable to check if the textfield is in focus and when to show the textfield and numberwidget (a widget which will keep count of the total chips, same like you asked in your question) or when to show the chipslist and textfield wraped in a wrap widget.
You can play around by changing the decoration of textfield to collapsed, to it look like the same as gmail.
Check this package, if you want to use custom package for ease.
I was facing a similar issue. I found a way to implement the Overflow count text.
Sample image
You basically have to paint the overflow text, and get its width like below
final TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: style),
textDirection: TextDirection.ltr,
textScaleFactor: WidgetsBinding.instance.window.textScaleFactor,
)..layout();
var textSize = textPainter.size;
textSize.width;
Then subtract that from the width available. Lets call it x.
Then create a sum of width for each row item(using TextPainter.layout() method mentioned above), till its value is less than x.
This way you'll know how many items can be shown in the row.
I have created a Flutter library to help with this.

How to access a widgets state from a stateful widget class Flutter

Sorry if this has been already answered somewhere else but I am new to Flutter. I have a toString method in my widget below that needs to access the state of the widget to output the string. The widget is a card that contains a text field and other text-related operations. To store information on what a user types into the card I need to get all the data into one string which toString returns.
class TextCard extends StatefulWidget {
_TextCardState cardState = _TextCardState();
TextCard({String text = ""}) {
cardState.textController.text = text;
}
#override
_TextCardState createState() => cardState = new _TextCardState();
String toString({DiagnosticLevel minLevel = DiagnosticLevel.debug}) {
return delimiter2 +
"TextCard" +delimiter3 +
cardState.getText() +
delimiter3 +
(cardState.center.toString()) +
delimiter3 +
cardState.bold.toString() +
delimiter3 +
cardState.italic.toString() +
delimiter3 +
cardState.size.toString() +
delimiter2;
}
}
The widget also takes in a string value to set the initial value of a text field in the state below
class _TextCardState extends State<TextCard> {
double size = 18;
bool bold = false;
bool italic = false;
bool center = false;
var textController = TextEditingController();
#override
Widget build(BuildContext context) {
return Container(
height: _cardSizeY,
width: _cardSizeX,
child: Card(
elevation: _elevation,
child: Center(
child: Column(children: [
ListTile(leading: Icon(Icons.text_fields)),
ButtonBar(children: [
IconButton(
icon: Icon(Icons.format_bold),
onPressed: () {
updateText(size, !bold, italic, center);
},
),
IconButton(
icon: Icon(Icons.format_italic),
onPressed: () {
updateText(size, bold, !italic, center);
},
),
Slider(
value: size,
max: 80,
min: 1,
onChanged: (size) {
updateText(size, bold, italic, center);
})
]),
TextField(
maxLines: null,
style: TextStyle(
fontWeight: (bold) ? FontWeight.bold : FontWeight.normal,
fontStyle: (italic) ? FontStyle.italic : FontStyle.normal,
fontSize: size),
textAlign: (center) ? TextAlign.center : TextAlign.start,
controller: textController,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
borderRadius: BorderRadius.all(Radius.circular(10)))))
]))));
}
void updateText(double size, bool bold, bool italic, bool center) {
setState(() {
this.size = size;
this.bold = bold;
this.italic = italic;
this.center = center;
});
}
String getText() {
return textController.value.text;
}
}
When I run this code I get the error the create state function returned an old invalid state instance.
I have looked into putting the text controller into the _TextCardState() class but I would not be able to change the initial value of the TextField.
So I see what you are trying to do here but there are better ways to access the value of a textfield from outside of the class.
Instead of access your toString method from outside, which relies on a values from the private state class, I suggest a state management solution that will make this way easier and cleaner. You'll also have easier access to all those variables you need.
What you're doing here is not something that's meant to be done, which is why you're getting those state errors.
_TextCardState cardState = _TextCardState();
Here's a way to do it using GetX.
All your data will live in a GetX Controller class below and will be used in your now stateless TextCard widget.
class Controller extends GetxController {
var textController = TextEditingController();
String textfieldString = '';
double size = 18;
bool bold = false;
bool italic = false;
bool center = false;
#override
void onInit() {
super.onInit();
// updates the value of textfieldString anytime the user types
textController.addListener(() {
textfieldString = textController.text;
debugPrint(textController.text);
});
}
// this method lives in this class and is accessible from anywhere. The
// only thing not clear is what delimier2 is and where it comes from
// toString is not a good name because that is an overridden method that lives
// in most Dart classes
String buildString({DiagnosticLevel minLevel = DiagnosticLevel.debug}) {
return delimiter2 +
"TextCard" +delimiter3 +
textfieldString +
delimiter3 +
(center.toString()) +
delimiter3 +
bold.toString() +
delimiter3 +
italic.toString() +
delimiter3 +
size.toString() +
delimiter2;
}
// single responsibility methods as opposed to firing one big function
// multiple times when its only affecting one variable
void toggleBold() {
bold = !bold;
update();
}
void toggleItalic() {
italic = !italic;
update();
}
void toggleCenter() {
center = !center;
update();
}
void updateSize(double sliderValue) {
size = sliderValue;
update();
}
}
Put this in your main before running your app. Can be done anywhere as long as its before you try and access the controller.
Get.put(Controller());
And here is your TextCard widget
class TextCard extends StatelessWidget {
#override
Widget build(BuildContext context) {
final controller =
Get.find<Controller>(); // finding the initalized controller
return Container(
height: _cardSizeY,
width: _cardSizeX,
child: Card(
elevation: 20,
child: Center(
child: Column(
children: [
ListTile(leading: Icon(Icons.text_fields)),
ButtonBar(children: [
IconButton(
icon: Icon(Icons.format_bold),
onPressed: () {
controller.toggleBold();
},
),
IconButton(
icon: Icon(Icons.format_italic),
onPressed: () {
controller.toggleItalic(); // accessing method via controller
},
),
// GetBuilder rebuilds children when value of controller variable changes
GetBuilder<Controller>(
builder: (_) {
return Slider(
value: controller
.size, // accessing size in other class via controller
max: 80,
min: 1,
onChanged: (value) {
controller.updateSize(value);
});
},
)
]),
GetBuilder<Controller>(
builder: (_) {
return TextField(
maxLines: null,
style: TextStyle(
fontWeight: (controller.bold)
? FontWeight.bold
: FontWeight.normal,
fontStyle: (controller.italic)
? FontStyle.italic
: FontStyle.normal,
fontSize: controller.size),
textAlign: (controller.center)
? TextAlign.center
: TextAlign.start,
controller: controller.textController,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
),
);
},
)
],
),
),
),
);
}
}
So where ever you are in your app where you need that function, find the controller and get your value.
final controller = Get.find<Controller>():
final newString = controller.buildString();
This will be easier and use less memory because TextCard is now stateless.