I want to make an editable TextWidget in flutter but I don't really know how to go around it, I did some research, but still can't find a good solution.
Here's my sample code below.
I have a variable called
int qty = 1;
and so I called the variable in TextWidget
Column(
children: [
Text(
"${qty}",
style: TextStyle(),
)
],
),
I want to have these features that make user tab on the value to change it if they want, upon tap, a pop-up dialog will show to give the user the ability to change the existing value to whatever the user wants.
Please if anyone knows how, please help.
You will need a statfull widget to call setState and make the UI update with the new value stored in your qty variable. (I'am assuming that you are not using any state managment).
I wrote a possible solution for what you need.
Let look into some considerations:
Text will show whatever is in the qty as long we call setState after (or do it inside) we change the value of qty.
You need some widget to detect your tap. If you want to the text be 'clicable' then it should be wraped inside that widget.
The onTap/onPress call back of that widget should show a new widget. For this you can use the already made showDialog() and pass it a Dialog Widget. in here you will put your ui for that.
In some point of that UI you need to introduce the new value. So you can use a simple TextField that will save the introduced value, where you can assign it to qty, without forgetting to call setState! Note that it deal with strings, so you neet to do an int.parse() ou double.parse accordingly to you qty var type.
And I think that's it.
The could be other ways of doing it. This is a good and simple approach for your need.
I wrote a piece of code to help or somelse how is trying to do it:
InkWell(
// can be gesture detector, button, etc
onTap: () => showDialog(
context: context,
builder: (context) => Dialog(
child: Container(
color:
Colors.white60, // change it accordingly to you
height: 80, // change it accordingly to you
width: 200, // change it accordingly to you
child: Column(
children: [
const Text('Change your value here'),
TextField(
decoration:
InputDecoration(hintText: qty.toString()),
onChanged: (insertValue) => setState(() {
qty = int.parse(insertValue);
}),
// you can use other callBack function (like onComplete,
// onSaved), wich is more eficient than calling setState eveytime,
// but you have to do the needed adtaptions. Like onSave
// needs a key to call the save function. is easy just google it.
),
],
)),
)),
child: Text(
"${qty}",
),
),
What you are probably looking is a DropdownButton.
You would have something like this:
int qty = 1;
List<int> listOfValues = [1,2,3,4];
and then in your column you would have
DropdownButton<int>(
// This are the list of items that will appear in your dropdown menu.
// items is all the options you want your users to be able to select from,
// and it take a list of `DropdownMenuItem`. So instead of creating a `DropdownMenuItem`
// for each of the items in `listOfValues`, we iterate through it and return
// a `DropdownMenuItem`
items: listOfValues
.map((item) => DropdownMenuItem<int>(
value: item,
child: Text('$item'),
))
.toList(),
value: qty,
onChanged: (value) {
if (value != null) {
setState(() {
qty = value;
});
}
},
),
For more information on DropDownButton, check the following links:
https://api.flutter.dev/flutter/material/DropdownButton-class.html
https://www.youtube.com/watch?v=K8Y7sWZ7Q3s
Note: In a scenario where you want to increase the quantity of an item, like in a shopping cart, maybe having a button increment qty by 1 would be better.
Related
Goodmorning,
I'm developing an app with flutter but I'm facing some problems with Provider (I think something miss in my knowledge).
My app fetch data from my API and displays them in listview.
In whole app I have different screen which displays different data type in listview and now I want to create filtering logic.
To avoid rewrite same code multiple times I thought to create one screen to reuse for filtering purposes but I'm facing problems with state management.
What I did:
create base model for filter information
`
enum FilterWidget { TEXT_FIELD, DROPDOWN } //to resolve necessary Widget with getWidget() (to implement)
class FilterBaseModel with ChangeNotifier {
String? value= 'Hello';
FilterWidget? widgetType;
FilterBaseModel(this.value, this.widgetType);
onChange() {
value= value== 'Hello' ? 'HelloChange' : 'Hello';
notifyListeners();
}
}
`
One screen for display filters depending on request
List<FilterBaseModel> filters = [];
FilterScreen() {
//Provided from caller. Now here for test purposes
filters.add(FilterBaseModel('Filter1', FilterWidget.TEXT_FIELD));
filters.add(FilterBaseModel('Filter2', FilterWidget.TEXT_FIELD));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SafeArea(
minimum: EdgeInsets.symmetric(vertical: 15, horizontal: 15),
child: SingleChildScrollView(
child: Container(
height: 400,
child: Column(
children: filters
.map(
(e) => Consumer<FilterBaseModel>(
builder: (_, filter, child) =>
ChangeNotifierProvider.value(
value: filter,
child: CustomTextField(
`your text` initialText: e.value,
onTap: () {
e.onChange();
filter.onChange();
},
),
),
),
)
.toList(),
))),
),
);
}
`
The problem is in Consumer and ChangeNotifier.value.
Screen works quite well: widget are displayed and callback are called, what is wrong? I need to use onChange method of both instance to update UI otherwhise method was called but widget is not rebuilt.
I know that probably putting consumer there is not right way but I tried also to put outside but doesn't work.
I expect to have one filter screen which receives in input filters list information, display them, handle their state managment and return their value.
P.S: this code now works, but I know is not the right way
Thank you for help!
EDIT
Have same behaviour without ChangeNotifierProvider.value. Therefore I'm more confused than before because still persist the double call to onChange for correct rebuilding.
More bit confused about ChangeNotifierProvider.value using...
I am using flutter rating bar package to give feedback to particular section then rating becomes back to 0. ho w can i persist the given rating constant .
Here is the screenshot of the app ...
RatingBar(
initialRating: 0,
direction: Axis.horizontal,
allowHalfRating: false,
itemCount: 5,
ratingWidget: RatingWidget(
full: const Icon(Icons.star,
color: Colors.orange),
half: const Icon(
Icons.star_half,
color: Colors.orange,
),
empty: const Icon(
Icons.star_outline,
color: Colors.orange,
)),
onRatingUpdate: (value) {}),
i think its a flutter behavior. in case we have much children on listview, then when we scrolldown, the widget that out of screen will marked as dirty widget.
then when we back scroll again , flutter will rebuild the widget.
Flutter already provide the solution here called
Destruction mitigation
you have to store the rating value to the object state. so when the widget get re-build , the value will automatically set from stored value.
other simple solution (not recomended)
you can extend the cache of listview.
ListView(
shrinkwrap: true,
cacheExtent : 99999999
children: [
RatingWidget(),
],
here the explanation to the chaceExtent
or another question in stackoverflow
What exactly does cacheExtent property in ListView.Builder do?
In onRatingUpdate save its value
onRatingUpdate :(val) async{
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setInt("teacherConcept", val);
}
Then in initstate get the value like
SharedPreferences prefs = SharedPreferences.getInstance();
int teacherConcept = prefs.get("teacherConcept", true);
Assign this teacher concept as the initial value of the ratingbar
That is because you have set initialRating to 0. So when you scroll down and come back up the whole widget rebuilds meaning for performance purposes it does not keep it in memory and the ratings get reset. So, what you can do is set the ratings set by the user into a variable through the onRatingUpdate and pass that variable into intitialRating
Map<String, int> ratings={'subjectClear':0, 'subjectVarieties':0}; // add other 8 as well
...
...
initialRating: ratings['subjectClear'],
onRatingUpdate: (value){
rating['subjectClear'] = value;
},
...
Still this data will be erased after the user restarts the app. So what you can do is store the data in a database and when the user returns substitute the values to the necessary variables.
i want to change displayed data in Flutter? I wrote a function changeDataForTest (only a function for testing the event), which should change the data displayed in Text.
But if I click on this, it isn't changed. The value of the displayed string only changes, if i add (context as Element).reassemble(); after calling the method. Is this the normal way to go, or is there a smoother way to solve my problem?
dynamic changeDataForTest(neuerWert) {
this.data = neuerWert;
}
Column(
children: [
Center(
child: Text(
this.data + this.wiegehts,
),
),
FlatButton(
textColor: Color(0xFF6200EE),
onPressed: () {
changeDataForTest('neuerWert');
(context as Element).reassemble();
},
)
],
)
Thanks
Lukas
If you're using only a small widget, you could use a StatefulWidget using the method:
setState(() {
// change your variable
})
If your widget is complex and has lots of different possible variables, I'll not recommend using setState as this method calls the build method every time is being used.
One simple and fast option, is to use ValueNotifier:
final myVariable = ValueNotifier(false); // where you can replace 'false' with any Object
and then, using it this way:
ValueListenableBuilder(
valueListenable: myVariable,
builder: (context, value, child) {
return Text(value); // or any other use of Widgets
},
);
myVariable.value = true; // if you're looking for to change the current value
finally, if you logic is truly complex and you need to scale, I'll recommend to use a StateManagement library like:
Provider
Riverpod
BloC
Others
You can find those libraries and examples over: https://pub.dev
I am working on a ToDo app an am trying to create five Text() widgets that hold the value of one TextformField(). I don't want to create lots of controllers, because the user is able to add more ToDo-fields through the press of a button. I always encounter the problem, that all Text() widgets hold the same string. Does anyone have an idea how I could store different strings through one TextEditingController and pass it to different Text() widgets in a compact code?
You can see the concept and problem below.
Thank you for helping me out!
The best solution is to create a widget, which is responsible for one TODO row (contains one Text widget and one TextEditingController). Then, instead of adding a Text widget and a TextEditingController, you can add a new instance of your new widget to the list of fields on the screen.
You can check this code.
TextEditingController test = TextEditingController();
Column(
children: [
TextField(
controller: test,
),
RaisedButton(
child: Text("abc"),
onPressed: () {
setState(() {
test.text = test.text + "abc";
});
},
),
RaisedButton(
child: Text("efg"),
onPressed: () {
setState(() {
test.text = test.text + "efg";
});
},
)
],
)
I'm using Radio widgets to set the user gender. These Radio button widgets are designed very well to handle the selection changes act accordingly, but I does not find a solution to somehow deselect one already selected option. I would like the gender radio group to be optional. But after user selects let's say female, he/she can change the selection to male, but can't deselect the female/male option. (So that none of the option is selected.)
There is only an onChanged property and it only changes if the gender changes. No such thing like onTap or something similar. So no matter if I check that gender if it's the same, onChanged won't be called.
For that reason I tried to use a GestureDetector which should solve this issue (with this I should be able to deselect the already selected option) but in case of radio widgets it does not work. I also tried to change it's behavior property but it did not help either.
Here is the function which I use to create gender radio option with a text in a Row.
Widget _buildRadioWithText(final genderChangeNotifier, final Gender genderParam, final String genderText) {
return Row(
children: <Widget>[
GestureDetector(
behavior: HitTestBehavior.translucent,
child: Radio<Gender>(
visualDensity: VisualDensity.compact,
value: genderParam,
groupValue: genderChangeNotifier.gender,
onChanged: (Gender gender) {
genderChangeNotifier.gender = gender;
print(gender.toString());
},
),
onTap: () {
if (genderChangeNotifier.gender == genderParam) {
genderChangeNotifier.gender = Gender.NA;
print("Not answered!");
}
},
),
GestureDetector(
onTap: () {
if (genderChangeNotifier.gender == genderParam) {
genderChangeNotifier.gender = Gender.NA;
print("Not answered!");
} else {
genderChangeNotifier.gender = genderParam;
print(genderParam.toString());
}
},
child: Text(
genderText,
style: TextStyle(
fontSize: _fontSize,
),
),
),
],
);
}
Function call:
_buildRadioWithText(genderChangeNotifier, Gender.FEMALE, "Female"),
_buildRadioWithText(genderChangeNotifier, Gender.MALE, "Male"),
genderChangeNotifieris just a provider to set and get the Gender value and notify listeners when a Gender is set.
final GenderNotifier genderChangeNotifier= Provider.of<GenderNotifier>(context);
GestureDetector's onTap works well when I tap on the Text widget. It selects then deselects the option just as I'd like to but in case of the Radio widget onTap is never called.
Any idea how to achieve the deselection when I tap/click on the Radio widget itself? And why the GestureDetector that wraps the Radio does not register the tap events?
The GestureDetector's onTap is not being called because the GestureDetector inside the Radio widget is taking priority. By default, when two GestureDetectors are competing for input, only one will "win". In this case, the winner is the one in the Radio. This article discusses "GestureArenas" and how you can allow both GestureDetectors to process input.
However, consider whether allowing the user to deselect the radio button is the correct solution. Radio buttons are designed not to be deselected once the user has selected an option. You might instead:
Offer a third, "I'd rather not say" option
Use toggleable buttons instead of a radio group
Use a dropdown menu instead of a radio group
See this answer for more info on the usability aspect of this.