TextField value is not getting updated in Flutter - flutter

class UserInputArea extends StatefulWidget {
#override
State<UserInputArea> createState() => _UserInputAreaState();
}
class _UserInputAreaState extends State<UserInputArea> {
#override
Widget build(BuildContext context) {
String convertedText='';
setState(() {
convertedText = Provider.of<UserText>(context, listen: true).convertedText;
print('convertedText :: $convertedText');
});
return Card(
elevation: 10,
child: Container(
padding: EdgeInsets.all(10),
child: TextField(
decoration: InputDecoration(hintText: convertedText.isNotEmpty ? convertedText : 'Enter text'),
keyboardType: TextInputType.multiline,
maxLines: 5,
onChanged: (value){
Provider.of<UserText>(context, listen: false).updateText(value);
},
),
),
);
}
}
Need to update hintText field whenever convertedText gets updated.
This update is happening only if screen refreshed somehow (In Appbar, if click on home-button-icon the data get updated in TextField), Using Provider package that should listen the changes and update the required feild, didnot work. So converted page to Stateful widget and addedd setState() & moved convertedText variable inside it. But still its not working, and not able to figure it out, what is exactly missing here? Anyhelp appreciated. Thanks in advance

Please use TextEditingController class
your code will be somthing like this
class UserInputArea extends StatefulWidget {
#override
State<UserInputArea> createState() => _UserInputAreaState();
}
class _UserInputAreaState extends State<UserInputArea> {
final TextEditingController nameController = TextEditingController();
#override
void initState() {
nameController.text = "test";
super.initState();
//Here you should write your func to change the controller value
Future.delayed(const Duration(seconds: 2), () {
nameController.text = 'test after chabging';
});
}
#override
Widget build(BuildContext context) {
return Card(
elevation: 10,
child: Container(
padding: EdgeInsets.all(10),
child: TextField(
controller: nameController,
decoration: InputDecoration(hintText: convertedText.isNotEmpty ? convertedText : 'Enter text'),
keyboardType: TextInputType.multiline,
maxLines: 5,
),
),
);
}
}
in the write it code above when you will enter the page the hint text will be test after 2 seconds the value will be "test after chabging" without any problem you do not need setState(() {}) I tired it and it works

I think that putting SetState() into the method and calling the method from onChanged could solve the issue. And moving it from Widget build. Something like this:
class UserInputArea extends StatefulWidget {
#override
State<UserInputArea> createState() => _UserInputAreaState();
}
class _UserInputAreaState extends State<UserInputArea> {
String convertedText='';
void _updateField() {
setState(() {
convertedText = Provider.of<UserText>(context, listen: true).convertedText;
print('convertedText :: $convertedText');
});
#override
Widget build(BuildContext context) {
return Card(
elevation: 10,
child: Container(
padding: EdgeInsets.all(10),
child: TextField(
decoration: InputDecoration(hintText: convertedText.isNotEmpty ? convertedText : 'Enter text'),
keyboardType: TextInputType.multiline,
maxLines: 5,
onChanged: (value){
Provider.of<UserText>(context, listen: false).updateText(value);
_updateField();
},
),
),
);
}
}

Related

bind the data of two widgets on the same level to each other and also to a global model through provider

Goal
I'm trying to use a top-level provider to pass model data downstream.
Also, I want some widgets on the same level share that model and affect each other at the same time, without using setState().
Code
Here is what I do to bind a Slider to a TextField
class NumberEntry extends StatefulWidget {
var data = 0;
NumberEntry({Key? key}) : super(key: key);
#override
State<NumberEntry> createState() => _NumberEntryState();
}
class _NumberEntryState extends State<NumberEntry> {
final _controller = TextEditingController();
#override
void initState() {
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var model = context.read<Model>();
return Row(
children: [
const Padding(
padding: EdgeInsets.all(4.0),
child: Text('Suffix'),
),
SizedBox(
width: 50,
child: TextField(
controller: _controller,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
onChanged: (input) {
var model = context.read<Model>();
model.setSuffix(int.parse(input));
},
)),
Padding(
padding: const EdgeInsets.all(2.0),
child: SizedBox(
width: 100,
child: Slider(
min: 0,
max: 100,
value: model.suffix.toDouble(),
onChanged: (input) {
var model = context.read<Model>();
model.setSuffix(input.toInt());
})),
),
],
);
}
}
My model looks like this
class Model extends ModelBase with ChangeNotifier, DiagnosticableTreeMixin {
int suffix = 0;
void setSuffix(int sx) {
suffix = sx;
notifyListeners();
}
}
Expectation
I expected that both widgets can bind to model.suffix and affect each other upon interaction. They also affect model.suffix independently.
Observation
The Slider does not move at all. The TextField does not affect the Slider.
Workaround
I had to bind widget.data to the TextField's controller and Slider's value, then use setState() to update them simultaneously in the onChange() callbacks to make it work as expected.
Question
The workaround seems a lot of redundancy right there, i.e. three copies of the same data.
Is this by design, or am I missing something obvious?

Using Dart / Flutter, how would I compare the values of two textfields to provide a time duration output?

Imagine I have 2 textfields that a user can input a 24 hour time in the format 1400 for example. Textfield 1 is the start time of a shift and textfield 2 is the end time. Ideally I'd like the duration between the two times to be displayed in a text widget when either textfields onSubmit is called or even if 1 of the textfields loses focus. I'm able to calculate and display the duration of 2 hard coded strings using the intl package with x.difference(y) but I'm struggling to get to this point with the textfields. Thanks for your help.
edit thinking about it after the initial post, the need for textfields isn't 100% required. the two times to compare could come from something like a datetime picker instead. what matters is that i've tried textfields, datetime picker and the time picker but can't arrive a a solution.
import 'package:flutter/material.dart';
class ActivityLog extends StatefulWidget {
#override
_ActivityLogState createState() => _ActivityLogState();
}
class _ActivityLogState extends State<ActivityLog> {
TextEditingController _controller1 = TextEditingController();
TextEditingController _controller2 = TextEditingController();
String duration = '0000';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Activity Log'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _controller1,
maxLength: 4,
maxLines: 1,
keyboardType: TextInputType.datetime,
decoration: InputDecoration(
labelText: 'Start time',
hintText: 'hhmm',
counterText: '',
),
),
TextField(
controller: _controller2,
maxLength: 4,
maxLines: 1,
keyboardType: TextInputType.datetime,
decoration: InputDecoration(
labelText: 'Finish time',
hintText: 'hhmm',
counterText: '',
),
),
Text('Duration: $duration'),
/*
can't figure out how to get the input from the textfields in the format of
HHmm (hours and minutes) and calculate the duration which is to be displayed
in the text widget above
*/
],
),
),
);
}
}
See if below works for you. Clearly I am not doing any validation, but this could be the start and you can build from that. Here is alsp an image of how it looks here.
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _controller1 = TextEditingController();
final _controller2 = TextEditingController();
var _result = "";
String addColon(String s) {
return s.substring(0, 2) + ":" + s.substring(2);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Column(
children: [
TextField(controller: _controller1),
TextField(controller: _controller2),
ElevatedButton(
onPressed: () {
var _d1 =
DateFormat.Hm().parse(addColon(_controller1.text));
var _d2 =
DateFormat.Hm().parse(addColon(_controller2.text));
setState(() {
_result =
'${_d2.difference(_d1).inSeconds.toString()} seconds';
});
},
child: const Text("Diff")),
const SizedBox(height: 50),
Text(_result),
],
),
),
),
);
}
}

onChanged method with setState of TextField does not update the result?

I am experimenting with flutter and I have a very simple code as follows:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
static double d = 0;
static TextEditingController editingController = TextEditingController();
#override
void initState() {
editingController.addListener(() {
setState(() {});
});
super.initState();
}
#override
void dispose() {
editingController.dispose();
super.dispose();
}
Calc calc = Calc(d: d, e: editingController);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(25),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextField(
keyboardType: TextInputType.number,
onChanged: (value) {
setState(() {
if (value.isNotEmpty) {
d = double.parse(value);
} else if (value.isEmpty) {
d = 0;
}
});
},
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'(^(\d{1,})\.?(\d{0,2}))'),
),
],
),
TextField(
keyboardType: TextInputType.number,
controller: editingController,
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'(^(\d{1,})\.?(\d{0,2}))'),
),
],
),
Text(
'First Text Field Value + 2 = \n${calc.dString()}',
style: TextStyle(fontSize: 30, color: Colors.purpleAccent),
),
Text(
calc.eString(),
style: TextStyle(fontSize: 30, color: Colors.deepOrangeAccent),
),
],
),
),
),
);
}
}
class Calc {
final double d;
final TextEditingController e;
Calc({this.d, this.e});
String dString() {
double result = d + 2;
return result.toStringAsFixed(0);
}
String eString() {
return e.text;
}
}
As we can see I am passing both the text fields' values into another class for some math and getting the results. These results are displayed using the Text widgets.
For the 1st TextField, I have used onChange method, and for the 2nd TextField, I have used TextEditingController.
I get return value for 2nd TextField from the Calc class, but not for the 1st TextField!
I think I am missing something basic and I did not find any solution so far. Can you please help me what am I missing here.
1st of all, you are creating just a single object of Calc,
yes as you can see your 2nd textField update perfectly because it's using TextEditingController but for the 1st one, it just call once and become 2 because of dString(), while on 1st run d becomes 0 passed on Calc.
if you want to use Calc to update text, you can simply put it inside build method like this , i dont suggest it, you can use callBackMethod to handle this or use another TextEditingController.
Hope you get it now
#override
Widget build(BuildContext context) {
Calc calc = Calc(d: d, e: editingController);
return Scaffold(
body: Center(
Your Calc Object is not being affect by setState() call. To run be able to get value of the calcobject, run it in you onChanged() function.

Can't update list with setState() in Flutter

I have a list of objects that I can display in a ListView. Now I wanted to implement a search feature and only display the search result. When I try to do it using onChanged on TextField(or even Controller) it doesn't work. I tried to debug and he gets the list updated correctly but he doesn't update the Widget. But when I removed the onChanged and added a button and then called the same method that I was calling on onChanged everything worked.
The goal is to update the widget as the user writes in the text field.
I would be happy to get some help
My full code :
import 'package:flutter/material.dart';
import 'package:hello_fridge/single_ingredient_icon.dart';
import 'package:string_similarity/string_similarity.dart';
import 'entities/ingredient.dart';
class IngredientsContainer extends StatefulWidget {
const IngredientsContainer({Key? key}) : super(key: key);
#override
_IngredientsContainerState createState() => _IngredientsContainerState();
}
class _IngredientsContainerState extends State<IngredientsContainer> {
late List<Ingredient> ingredients;
final searchController = TextEditingController();
#override
void dispose() {
// Clean up the controller when the widget is disposed.
searchController.dispose();
super.dispose();
}
void updateResults(String newValue) {
if (newValue.isEmpty) {
ingredients = Ingredient.getDummyIngredients();
} else {
print("new Value = $newValue");
ingredients = this.ingredients.where((ing) {
double similarity =
StringSimilarity.compareTwoStrings(ing.name, newValue);
print("$similarity for ${ing.name}");
return similarity > 0.2;
}).toList();
ingredients.forEach((element) {
print("found ${element.name}");
});
}
setState(() {});
}
Widget _searchBar(List<Ingredient> ingredients) {
return Row(
children: <Widget>[
IconButton(
splashColor: Colors.grey,
icon: Icon(Icons.restaurant),
onPressed: null,
),
Expanded(
child: TextField(
controller: searchController,
onChanged: (newValue) {
updateResults(newValue);
},
cursorColor: Colors.black,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.go,
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 15),
hintText: "Search..."),
),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: IconButton(
icon: Icon(
Icons.search,
color: Color(0xff9ccc65),
),
onPressed: () {
updateResults(searchController.text);
},
),
),
],
);
}
#override
void initState() {
this.ingredients = Ingredient.getDummyIngredients();
super.initState();
}
#override
Widget build(BuildContext context) {
return Material(
child: Column(children: [
Expanded(flex: 1, child: _searchBar(this.ingredients)),
Expanded(flex: 4, child: IngredientsGrid(this.ingredients))
]),
);
}
}
class IngredientsGrid extends StatelessWidget {
List<Ingredient> ingredients;
IngredientsGrid(this.ingredients);
List<Widget> _buildIngredients() {
return this.ingredients.map((ing) => SingleIngredientIcon(ing)).toList();
}
// const IngredientsGrid({
// Key? key,
// }) : super(key: key);
#override
Widget build(BuildContext context) {
this.ingredients.forEach((ing) => print(ing.name! + ","));
return ListView(
children: <Widget>[
GridView.count(
crossAxisCount: 4,
// physics: NeverScrollableScrollPhysics(),
// to disable GridView's scrolling
shrinkWrap: true,
// You won't see infinite size error
children: _buildIngredients()),
// ...... other list children.
],
);
}
}
Moreover, I keep getting this Warning :
"Changing the content within the composing region may cause the input method to behave strangely, and is therefore discouraged. See https://github.com/flutter/flutter/issues/78827 for more details".
Visiting the linked GitHub page wasn't helpful
The problem is that while you are correctly filtering the list but your TextController is not getting assigned any value.
So, no value is getting assigned to your TextField as the initial value and hence the list again filters to have the entire list.
To solve this just assign the TextController the newValue like this.
void updateResults(String newValue) {
if (newValue.isEmpty) {
ingredients = Ingredient.getDummyIngredients();
} else {
print("new Value = $newValue");
ingredients = this.ingredients.where((ing) {
double similarity =
StringSimilarity.compareTwoStrings(ing.name, newValue);
print("$similarity for ${ing.name}");
return similarity > 0.2;
}).toList();
ingredients.forEach((element) {
print("found ${element.name}");
});
}
// change
searchController = TextEditingController.fromValue(
TextEditingValue(
text: newValue,
),
);
setState(() {});
}
If it throws an error then remove final from the variable declaration, like this :
var searchController = TextEditingController();

Flutter setting a value of TextFormField according to setState of a widget variable

how can i set the value of a TextFormField from a widget variable when this variable changed according to a setstate action after building the widget and not by the initial value of this textformfeild
what i know that i can change its value by using the TextEditingController but i have too many TextFormField and creating the TextEditingController take a lot of code by using the traditional way by adding them the whole widget:
1-Create a TextEditingController.
2-Connect the TextEditingController to a text field.
3-Create a function to update the latest value.
4-Listen to the controller for changes.
is there any way to build this controller and do those steps inside the code of the TextFormField only? or another way ?
place controler in
TextFormField(
controller: TextEditingController(text: daytask),....
then place setstate() txt=''; like this
setState(() { txt='' }
Best way to do this is to simply make a widget. For this one, here is the code that i would use
class StateTextField extends StatelessWidget {
final String text;
const StateTextField({Key key, this.text = ''}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextField(
controller: TextEditingController(text: text),
);
}
}
that should do the work. If u want to pass the controller, u can pass it too. And to use it, just call the class in build method.
Similar example with TextFormField, TextEditingController, clickable suffixIcon for a selectDate showDatePicker (this is a standard material "lambda" dialog) where setState is updating the initial set date (not by the initialValue, rather by the controller parameter) with a new date.
import 'package:flutter/material.dart';
class CreateOrderForm extends StatefulWidget {
const CreateOrderForm({Key? key}):super(key: key);
#override
CreateOrderFormState createState() => CreateOrderFormState();
}
class CreateOrderFormState extends State<CreateOrderForm>
{
final _formKey = GlobalKey<FormState>();
late DateTime selectedDate;
#override
void initState() {
super.initState();
selectedDate = DateTime.now();
print("initState selectedDate = ${selectedDate.toLocal()}");
}
#override
Widget build(BuildContext context) {
print("build prepare selectedDate: ${selectedDate.toLocal()}");
return Form(
key: _formKey,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
//initialValue: "${selectedDate.toLocal()}",
controller: TextEditingController(text: "${selectedDate.toLocal()}"),
decoration: InputDecoration(
icon: Icon(Icons.event),
border: OutlineInputBorder(),
labelText: "Assignment Date & Time",
suffixIcon: IconButton(
onPressed: () => _selectDate(context),
icon: Icon(Icons.event))
),
),
const Padding(padding: EdgeInsets.only(top: 8)),
Align(
alignment: Alignment.bottomRight,
child: ElevatedButton(
onPressed: ((){}),
child: Text("Submit")),
)
],
),
),
)
);
}
_selectDate(BuildContext context) async{
await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(1970),
lastDate: DateTime(9999)
).then((value){
if(value != null && value != selectedDate)
{
this.setState(() {
selectedDate = value;
print("New date ${selectedDate.toLocal()}");
});
}
});
}
}