How to manually delete a textfield in flutter? - 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: "");
}
...
}

Related

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
},
);
}
}

ListView not updating after filtering Flutter

I am trying to filter a listview. I am able to see that data is filtered but I don't get anything at the screen. Probably it is just a simple thing.
Part of the code:
final messageBubble = MessageBubble(
document: nameDocument,
name: gaName,
);
messageBubbles.add(messageBubble);
filtermessageBubbles= messageBubbles;
(...)
FloatingSearchBar.builder(
pinned: true,
itemCount: filtermessageBubbles.length,
padding: EdgeInsets.only(top: 10.0),
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: filtermessageBubbles[index],
);
},
onChanged: (String value) {
setState(() {
filtermessageBubbles = messageBubbles.where((item) => item.name.toLowerCase().contains(value.toLowerCase())).toList();
print(filtermessageBubbles.length);
print(filtermessageBubbles);
});
},
and when I run it, I am able to see that filteredmessageBubbler.length reduces its number, but I don't see any change in the virtual device.
Thanks in advance!

typeAhead textfield in flutter web

I am using the typeAhead text field for auto-completion but the onSuggestionSelected function does not work. I am using flutter web. No selection is made and listView shows am hovering over an item above the one am actually hovering.
Below is the dialog, in this case the mouse pointer is actually below 'FIN' in the listView. I am using the typeAhead field in a dialog.
This is the code below.
TypeAheadFormField(
//initialValue: 'bleh',
textFieldConfiguration: TextFieldConfiguration(
controller: _typeAheadController, //this.
decoration: InputDecoration(
hintText: 'Programme(required)' //label
)),
suggestionsCallback: (pattern) {
return programme
.where((String dc) => dc
.toLowerCase()
.contains(pattern.toLowerCase()))
.toList(); //|| dc.toLowerCase().contains(other.toLowerCase())).toList();
},
itemBuilder: (context, String suggestion) {
return ListTile(
title: Text(suggestion),
);
},
transitionBuilder:
(context, suggestionsBox, controller) {
return suggestionsBox;
},
onSuggestionSelected: (String suggestion) {
_typeAheadController.text = suggestion; //this
print(suggestion + ' selected bith');
},
validator: (value) {
if (value.isEmpty ||
programme.indexOf(value) == -1) {
return 'Please select a course offered in the university you selected!';
}
},
onSaved: (value) => this._selectedCity =
value, //<--------- upload value
)
'programme' is the list of strings returned in which string 'dc' is found. 'print(suggestion + ' selected bith');' does not print a thing to show I never actually have onSelection run at all. Anyone know a work around? thank you.
Dialog:
You can use 2 methods to make it workable:
Method 1:
Use GestureDetector inside itemBuilder as below.
itemBuilder: (context, String suggestion) {
return GestureDetector(
onPanDown: (_) {
print(suggestion);
},
child: ListTile(
dense: true,
title: Text(suggestion),
),
);
},
by doing this, you still need the 'onSuggestionSelected' method, but you can leave it blank.
Method 2:
itemBuilder: (context, String suggestion) {
return Listener(
child: Container(
color: Colors.black, // <-- this gets rid of the highlight that's not centered on the mouse
child: ListTile(
title: Text(suggestion),
),
),
onPointerDown: () {
print(suggestion);
// Do what you would have done in onSuggestionSelected() here
},
);
},
I got these answers from following link onSuggestionSelected not called in Web

Flutter: Autocomplete Textfield not working with custom data type

I'm trying to build a text field with autocomplete feature. And I'm using AutoComplete TextField package.
I have Users model class with fromMap and toMap methods. There's function which retrieves users form database and returns list of users List<Users>.
Here's the code which builds autocomplete field:
AutoCompleteTextField searchTextField = AutoCompleteTextField<Users>(
key: key,
clearOnSubmit: false,
suggestions: users,
style: TextStyle(color: Colors.black, fontSize: 16.0),
decoration: InputDecoration(
contentPadding: EdgeInsets.fromLTRB(10.0, 30.0, 10.0, 20.0),
hintText: "Search Name",
hintStyle: TextStyle(color: Colors.black),
),
itemFilter: (item, query) {
return item.name.toLowerCase().startsWith(query.toLowerCase());
},
itemSorter: (a, b) {
return a.name.compareTo(b.name);
},
itemSubmitted: (item) {
setState(() {
searchTextField.textField.controller.text = item.name;
});
},
itemBuilder: (context, item) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
item.name,
),
],
);
},
);
Q. Am I missing something or doing wrong?
NOTE:
The users object have list of users in correct format, I've printed to verify that.
As #pskink mentioned,
you are using autocomplete_textfield? i had a lot of problems with it, that disappeared when i switched to flutter_typeahead (much better documented package)
So I considered his suggestion, and move to flutter_typeahead package.
final TextEditingController _typeAheadController = TextEditingController();
List<String> usersList;
//find and create list of matched strings
List<String> _getSuggestions(String query) {
List<String> matches = List();
matches.addAll(usersList);
matches.retainWhere((s) => s.toLowerCase().contains(query.toLowerCase()));
return matches;
}
//gets user list from db
void _getUsersList() async {
usersList = await databaseHelper.getUsersList();
}
//the above code is defined in the class, before build method
//builds the text field
TypeAheadFormField(
textFieldConfiguration: TextFieldConfiguration(
controller: _typeAheadController,
decoration: InputDecoration(labelText: 'Select a User'),
suggestionsCallback: (pattern) {
return _getSuggestions(pattern);
},
itemBuilder: (context, suggestion) {
return ListTile(
title: Text(suggestion),
);
},
transitionBuilder: (context, suggestionsBox, controller) {
return suggestionsBox;
},
onSuggestionSelected: (suggestion) {
_typeAheadController.text = suggestion;
},
validator: (val) => val.isEmpty
? 'Please select a user...'
: null,
onSaved: (val) => setState(() => _name = val),
),
//function that pulls data from db and create a list, defined in db class
//not directly relevant but it may help someone
Future<List<String>> getUsersList() async {
Database db = await instance.database;
final usersData = await db.query("users");
return usersData.map((Map<String, dynamic> row) {
return row["name"] as String;
}).toList();
}
PS: One thing I miss about autocomplete_textfield is the way that we can pass multiple parameters, as we can inherit from our own custom model e.g user model. I know it is possible with this, but I'm new to this so still unable to make it work! :(
I was having the same problem, the solution was to put a bool and show a CircularProgressIndicator until all the data in the list is loaded, and thus rendering the AutoCompleteTextField
Ex.:
_isLoading
? CircularProgressIndicator ()
: searchTextField = AutoCompleteTextField <User> (your component here)

How to remove a TextField from ListView when onPressed button?

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
})
),
),
);
}
)