How to remove a TextField from ListView when onPressed button? - flutter

How to remove the TextField when user click on "clear icon" button ? (not just clear the Text of TextField)
User Story
The user click on a button to add player. (Technically this button add TextField)
The user can write the name of player on TextField.
The user click on a "clear icon" button to remove current TextField (opposite of add function).
new ListView.builder(
padding: EdgeInsets.all(0),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: 5,
itemBuilder: (context, index) {
print(index);
return TextField(
maxLength: 20,
decoration: InputDecoration(
labelText: "Player ${index+1}",
counterText: "",
prefixIcon: const Icon(Icons.person),
suffixIcon: new IconButton(
icon: Icon(Icons.clear),
onPressed: () =>
setState(() {
this.dispose(); // -----Doesn't work----
})
),
),
);
}
),
For example, user set "John" on Player 4 if user click on "clear button" then Player 4 TextField is deleted. It will remain only 4 TextField

Facts I assume:
you want to be able to delete (or add) a field from (to) the list
you want the values of the remaining fields to remain when you delete the field
the list can be larger than 5
Solution:
If you want all the above to be true, then you actually need to track the TextEditingControllers of the TextFields, instead of the text fields themselves. This is because the value of the TextField is actually stored in the TextEditingController (which is created anew on the fly if you do not supply it for each widget). Check this out:
import 'package:flutter/material.dart';
// needs to be StatefulWidget, so we can keep track of the count of the fields internally
class PlayerList extends StatefulWidget {
const PlayerList({
this.initialCount = 5,
});
// also allow for a dynamic number of starting players
final int initialCount;
#override
_PlayerListState createState() => _PlayerListState();
}
class _PlayerListState extends State<PlayerList> {
int fieldCount = 0;
int nextIndex = 0;
// you must keep track of the TextEditingControllers if you want the values to persist correctly
List<TextEditingController> controllers = <TextEditingController>[];
// create the list of TextFields, based off the list of TextControllers
List<Widget> _buildList() {
int i;
// fill in keys if the list is not long enough (in case we added one)
if (controllers.length < fieldCount) {
for (i = controllers.length; i < fieldCount; i++) {
controllers.add(TextEditingController());
}
}
i = 0;
// cycle through the controllers, and recreate each, one per available controller
return controllers.map<Widget>((TextEditingController controller) {
int displayNumber = i + 1;
i++;
return TextField(
controller: controller,
maxLength: 20,
decoration: InputDecoration(
labelText: "Player $displayNumber",
counterText: "",
prefixIcon: const Icon(Icons.person),
suffixIcon: IconButton(
icon: Icon(Icons.clear),
onPressed: () {
// when removing a TextField, you must do two things:
// 1. decrement the number of controllers you should have (fieldCount)
// 2. actually remove this field's controller from the list of controllers
setState(() {
fieldCount--;
controllers.remove(controller);
});
},
),
),
);
}).toList(); // convert to a list
}
#override
Widget build(BuildContext context) {
// generate the list of TextFields
final List<Widget> children = _buildList();
// append an 'add player' button to the end of the list
children.add(
GestureDetector(
onTap: () {
// when adding a player, we only need to inc the fieldCount, because the _buildList()
// will handle the creation of the new TextEditingController
setState(() {
fieldCount++;
});
},
child: Container(
color: Colors.blue,
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
'add player',
style: TextStyle(
color: Colors.white,
),
),
),
),
),
);
// build the ListView
return ListView(
padding: EdgeInsets.all(0),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
children: children,
);
}
#override
void initState() {
super.initState();
// upon creation, copy the starting count to the current count
fieldCount = widget.initialCount;
}
#override
void dispose() {
super.dispose();
}
#override
void didUpdateWidget(PlayerList oldWidget) {
super.didUpdateWidget(oldWidget);
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
}
}
With the above, you can:
start the app
change player 2 to 'bob'
change player 3 to 'steve'
change player 4 to 'charles'
delete player 3
observe that player 2 is 'bob' and the new player 3 is 'charles'
I think this is what you are looking for here.

You can have a property to check wheter the user clicked that button and depending on the value you show/hide the TextField. Below I just use a boolean property if user click on X button then I set hideField to true and the TextField will be replaced with a zero sized widget.
new ListView.builder(
padding: EdgeInsets.all(0),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: 5,
itemBuilder: (context, index) {
print(index);
bool hideField = false; // I added it here
return hideField ? SizedBox.shrink() : TextField(
maxLength: 20,
decoration: InputDecoration(
labelText: "Player ${index + 1}",
counterText: "",
prefixIcon: const Icon(Icons.person),
suffixIcon: new IconButton(
icon: Icon(Icons.clear),
onPressed: () =>
setState(() {
hideField = true; // Now it works
})
),
),
);
}
)

Related

How to manually delete a textfield in flutter?

I am making a growable Textfield widget that a user can add as many textfield as he wants.
I am using a list of textfields in this case. If user presses button to add textfield , it will be added to the list .
Also have created a function to delete the textfield if user wants and ima useing listName.removeAt() method for this . But when i delete a textfield which got some value there is a mismatch . I am deleting the textfield of that index but the value it holds shifts to another field.
Where i implement the code is :
Consumer(
builder: (ctx, ref, child) {
final customCourse =
ref.watch(customCourseTypeNotifierProvider);
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: customCourse.customCourses.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: MinimalInputField(
onChanged: (String value) {
},
hintText: "",
suffixIcon: IconButton(
icon: const Icon(Icons.remove),
color: Colors.red,
onPressed: () {
ref
.read(customCourseTypeNotifierProvider)
.removeCourse(index);
},
),
),
);
},
);
},
),
In type_controller.dart
class CourseTypeNotifier extends ChangeNotifier {
List<CustomCourseModel> customCourses = [];
void addCourse() {
customCourses.add(
const CustomCourseModel(title: "", description: ""),
);
notifyListeners();
}
void removeCourse(int index) {
customCourses.removeAt(index);
notifyListeners();
}
When text in MinimalInputField changes, the model is not updated and when the model is changed, text in MinimalInputField is not updated.
Using TextField could go something like this:
TextField(
controller: TextEditingController()..text = customCourse.customCourses[index].title!,
onChanged: (String value) {
ref.read(customCourseTypeNotifierProvider).updateCourse(index, value);
},
...
)
class CourseTypeNotifier extends ChangeNotifier {
...
void updateCourse(int index, String title) {
customCourses[index] = CustomCourseModel(title: title, description: "");
}
...
}

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.

Why AutoCompleteTextField is not showing any suggestion in Flutter?

I am new to Flutter and currently working on a project where I need to show user a list of matched members so that a user can easily select one of them. For that I use AutoCompleteTextField. It is working fine as long as provided by already fetched list of members to it's suggestion property. But I wonder, why it's not working when I put it under BlocBuilder. Event hits on textChanged method and the state also returns a list but the suggestions are invisible.
Widget autoCompleteSearchBar() {
return BlocBuilder<OrderInfoBloc, MyOrderInfoStates>(
builder: (context, state) {
return AutoCompleteTextField<Member>(
clearOnSubmit: false,
style: TextStyle(
color: Colors.black,
fontSize: 16,
),
decoration: InputDecoration(
hintText: 'Search Member Here..',
border: InputBorder.none,
suffixIcon: IconButton(
icon: Icon(Icons.cancel),
iconSize: 20,
color: Colors.yellow[700],
onPressed: () {
_autoCompleteController.text = "";
},
),
contentPadding: EdgeInsets.fromLTRB(10, 30, 10, 20),
hintStyle: TextStyle(color: Colors.grey),
),
keyboardType: TextInputType.text,
controller: _autoCompleteController,
textChanged: (value) {
context.read<OrderInfoBloc>().add(SearchTextChanged(text: value));
},
itemSubmitted: (item) async {
_autoCompleteController.text = state.radioGroupValue == 'By Code'
? item.memberNo
: item.memberName;
context.read<OrderInfoBloc>().add(SelectedMember(member: item));
},
key: _key,
suggestions: state.membersList,
itemBuilder: (context, item) {
print(item);
// return state.radioGroupValue == 'By Code'
// ? autoCompleteSearchBarRow(
// item: item.memberNo, icon: Icon(Icons.person))
// : autoCompleteSearchBarRow(
// item: item.memberName, icon: Icon(Icons.person));
return autoCompleteSearchBarRow(
item: item.memberNo, icon: Icon(Icons.person));
},
itemFilter: (item, query) {
print(query);
// bool _itemFilter;
// if (_autoCompleteController.text.isNotEmpty) {
// _itemFilter = state.radioGroupValue == 'By Code'
// ? item.memberNo
// .toLowerCase()
// .startsWith(query.toLowerCase())
// : item.memberName
// .toLowerCase()
// .startsWith(query.toLowerCase());
// } else {
// _autoCompleteController.text = '';
// _itemFilter = false;
// }
// return _itemFilter;
return item.memberNo.toLowerCase().startsWith(query.toLowerCase());
},
itemSorter: (a, b) {
// return state.radioGroupValue == 'By Code'
// ? a.memberNo.compareTo(b.memberNo.toLowerCase())
// : a.memberName.compareTo(b.memberName.toLowerCase());
print(b);
return a.memberNo.compareTo(b.memberNo.toLowerCase());
},
);
}
);
}
Widget autoCompleteSearchBarRow(
{#required String item, #required Icon icon}) {
return ListTile(
leading: icon,
title: Text(item),
);
}
Use the flutter_typeahead package which works well with flutter bloc
Now, come to the bloc side you don't need to wrap your autocomplete widget with blocbuilder cause if you do so, the bloc will always repaint the widget whenever an event fires. so in your case when you are typing in the text box, event fires and bloc rebuild the widget and because of that suggestion don't show up and even if you see suggestion they will be gone once the corresponding bloc state occurs and rebuild the widget
the recommended solution would be seen below
Don't add any state to get suggestions just return the result or records from event as below. (below function added to Cubit file)
Future<List<Item>> getProductItemsBySearchString(String item) async {
return await itemRepository.getItemsByName(item);
}
as you can see above I am returning item records directly from the getProductItemsBySearchString() event method (no bloc state)
Then use It like below
class ItemScreen extends StatelessWidget {
// then you can call bloc event in function as below
Future<List<Item>> getItemSuggestionsList(
BuildContext context, String text) async {
final bloc = context.read<ItemCubit>();
List<Item> data = await bloc.getProductItemsBySearchString(text);
if (data != null) {
return data;
} else {
return null;
}
}
#override
Widget build(BuildContext context) {
return TypeAheadField(
getImmediateSuggestions: true,
textFieldConfiguration: TextFieldConfiguration(
controller: _itemEditingController,
autofocus: false),
suggestionsCallback: (pattern) {
// call the function to get suggestions based on text entered
return getItemSuggestionsList(context, pattern);
},
itemBuilder: (context, suggestion) {
// show suggection list
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
title: Text(suggestion.name),
trailing: Text(
'Item Code: ${suggestion.code}',
),
),
);
},
onSuggestionSelected: (suggestion) {
// get selected suggesion
},
);
}
}

Connecting multiple text fields to act like a single field in Flutter

We are busy creating a mobile application in Flutter that relates to credit cards. In our design, we have planned to capture the numeric fields for the card in VIN number and expiry date using 4 separate fields, as shown in the below image (I am sure you have seen similar implementations on other apps):
What is important here is that these 4 fields still need to act like a single field, namely if you type it needs to jump to the next one, if you delete or press left it needs to jump to the previous one.
We have tried two different approaches that are both giving us issues:
Use four different fields - This worked somewhat well, but became buggy really fast when the user deletes or tried to navigate between numbers.
Use a single input field with expanded letterSpacing four lines drawn underneath - This again works somewhat well, but since the font we are using is not monospaced (letters and numbers that are all the same size) the numbers do not really line up properly with the lines underneath, making it look rather terrible.
I have done some searching, but have yet to find someone who has done something similar that have posted about it online. It is somewhat difficult to make Google understand what I am asking, which is also adding to the difficulty in finding a solution.
Does anyone know of an example out there that might point me in the right direction, or does anyone on here perhaps have any clever ideas about how this might be achieved? Any advice would be greatly appreciated!
PS:
Although it is a but convoluted, please see our multiple input field code below:
Multiple texts fields:
import 'package:creditcardcurator/widgets/textFields/single_number_textfield.dart';
import 'package:flutter/material.dart';
import 'package:print_color/print_color.dart';
var focusNodes;
bool getFirstFieldFocus = true;
class ExpiryDateTextField extends StatefulWidget {
final List<TextEditingController> controllers;
final Function onChangeF;
final Function onComplete;
final bool startFocus;
final FocusNode focusNode;
final bool isDisbaled;
void requestFocus() {
Print.red(getFirstFieldFocus.toString());
if (getFirstFieldFocus) focusNodes[0].requestFocus();
}
ExpiryDateTextField(
{this.controllers,
this.onChangeF,
this.onComplete,
this.startFocus = false,
this.focusNode,
this.isDisbaled = false});
#override
_ExpiryDateTextFieldState createState() => _ExpiryDateTextFieldState();
}
class _ExpiryDateTextFieldState extends State<ExpiryDateTextField> {
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
#override
void initState() {
super.initState();
focusNodes = [
widget.focusNode,
FocusNode(),
FocusNode(),
FocusNode(),
];
getFirstFieldFocus = true;
if (widget.startFocus) widget.requestFocus();
}
#override
Widget build(BuildContext context) {
if (widget.startFocus) widget.requestFocus();
bool onSwitchChanged(value, context, index) {
getFirstFieldFocus = false;
if (index == 4) {
//if it is on the last text box, do nothing
} else {
if (widget.controllers[index].text.length > 0) {
index++;
FocusScope.of(context).requestFocus(focusNodes[index]);
widget.controllers[index].selection = TextSelection(
baseOffset: 0,
extentOffset: widget.controllers[index].text.length);
// FocusScope.of(context).focus
}
}
return true;
}
return Row(
children: <Widget>[
Expanded(
flex: 1,
child: SingleDigitTextField(
isDisbaled: widget.isDisbaled,
onChangedF: (value) {
widget.onChangeF(value, 0);
// print(this.toString(minLevel: DiagnosticLevel.debug));
onSwitchChanged(value, context, 0);
},
fNode: focusNodes[0],
cController: widget.controllers[0]),
),
Expanded(
flex: 1,
child: SingleDigitTextField(
isDisbaled: widget.isDisbaled,
onChangedF: (value) {
widget.onChangeF(value, 1);
onSwitchChanged(value, context, 1);
},
fNode: focusNodes[1],
cController: widget.controllers[1]),
),
Expanded(
flex: 1,
child: Text("/",
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.display2),
),
Expanded(
flex: 1,
child: SingleDigitTextField(
isDisbaled: widget.isDisbaled,
onChangedF: (value) {
widget.onChangeF(value, 2);
onSwitchChanged(value, context, 2);
},
fNode: focusNodes[2],
cController: widget.controllers[2]),
),
Expanded(
flex: 1,
child: SingleDigitTextField(
isDisbaled: widget.isDisbaled,
onChangedF: (value) {
widget.onChangeF(value, 3);
if (value != null && value.length != 0) {
try {
widget.onComplete();
} catch (e) {
print(e.toString());
}
}
onSwitchChanged(value, context, 3);
},
fNode: focusNodes[3],
cController: widget.controllers[3],
onComplete: widget.onComplete,
),
),
],
);
}
}
Single text field:
import 'package:creditcardcurator/utils/page_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class SingleDigitTextField extends StatelessWidget {
final Function onChangedF;
final FocusNode fNode;
final TextEditingController cController;
final Function onComplete;
final bool isDisbaled;
// final Widget child;
// DollarTextField({this.child});
SingleDigitTextField(
{this.onChangedF,
this.fNode,
this.cController,
this.onComplete,
this.isDisbaled = false});
void defaultFunction() {
print("default function actioned");
}
#override
Widget build(BuildContext context) {
return Container(
// margin: const EdgeInsets.all(8.0),
padding: const EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 0.0),
width: 50,
child: TextField(
enabled: !isDisbaled,
inputFormatters: [
WhitelistingTextInputFormatter(new RegExp('[0-9,.]'))
],
controller: cController,
onTap: () {
// this.cController.selection = TextSelection.fromPosition(
// TextPosition(offset: this.cController.text.length)); //cursor to end of textfield
cController.selection = TextSelection(
baseOffset: 0,
extentOffset: cController.text.length); //to begining of textfield
},
focusNode: fNode,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: getd3FontSize(context),
fontFamily: getFontFamily(context),
color: Colors.white,
),
maxLength: 1,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(bottom: -20),
counter: null,
helperStyle: TextStyle(
color: Colors.transparent,
), //hiding the max length hint text
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
),
keyboardType:
TextInputType.numberWithOptions(signed: true, decimal: true),
// keyboardType: TextInputType.number,
onChanged: (e) {
onChangedF(e);
},
onEditingComplete: () {
try {
onComplete();
} catch (e) {
print(e.toString());
}
},
),
);
}
}

How to deselect rest index with list in flutter?

I have a list of buttons in row , there are 4 items or buttons in list,
I have made a model class for creating data for each button, the list is model type.
all working fine, I want to highlight or select the button, on which I pressed and rest index button should be deselected.
I'm able to highlight the pressed button but unable to deselect the rest buttons.
code
//Button model class
class TopButtonModel{
String buttonLable;
bool isOnTp;
TopButtonModel({this.buttonLable, this.isOnTp});
}
//initializing list with these data
int myIndex=0;
List<TopButtonModel> buttons = [
TopButtonModel(
buttonLable: 'Skin',
isOnTp: true
),
TopButtonModel(
buttonLable: 'Hair Style',
isOnTp: false
),
TopButtonModel(
buttonLable: 'Makeup',
isOnTp: false
),
TopButtonModel(
buttonLable: 'Handset & Feet',
isOnTp: false
),
];
buttonsRow() {
return Container(
color: Colors.white,
child: ListView.builder(
// Rpadding: const EdgeInsets.only(right: 9),
scrollDirection: Axis.horizontal,
itemCount: buttons.length,
itemBuilder:(context, index) {
// myIndex =in
return Padding(
padding: const EdgeInsets.only(right: 9,top: 9),
child: FlatButton(
color: buttons[index].isOnTp?Colors.indigo:Colors.grey[200],
onPressed: () {
print(index);
setState(() {
myIndex =index;
buttons[index].isOnTp =true;
});
// if (buttons.any((item) => item.isOnTp)) {
// setState(() {
// buttons[index].isOnTp = !buttons[index].isOnTp;
// });
// }
},
child: Text(buttons[index].buttonLable,
style: TextStyle(color: buttons[index].isOnTp?Colors.white:Colors.black),
)),
);
}
),
) ;
}
wanna achieve this
and what is happening
This should be as simple as this:
setState(() {
buttons[myIndex].isOnTp = false;
myIndex = index;
buttons[index].isOnTp = true;
});
Here is a working code example: Code Pen