strong textI'm implementing a contacts list search bar, where the user can search & select some contacts, but I need the selected contacts to be shown as selected while the user is still searching.
I achieved this by modifying the original list and the duplicate list which used in the searching process at the same time.
is this an Anti-Pattern and is there a better way to do it?
Here's what I'm doing with the search query:
void searchContacts([String? name]) {
if (name == null || name.isEmpty) {
searchedList.clear();
addAllContactsToSearchList();
return;
} else {
searchedList.clear();
originalContactsList!.forEach((contact) {
if (contact.name.toLowerCase().contains(name.toLowerCase())) {
searchedList.clear();
searchedList.add(contact);
return;
} else {
return;
}
});
return;
}
}
and here's the code for selecting a contact:
void _onChange(bool value, int index) {
final selectedContact = searchedList[index].copyWith(isSelected: value);
searchedList.removeAt(index);
setState(() {
searchedList.insert(index, selectedContact);
notifier.originalContactsList = notifier.originalContactsList!.map((e) {
if (e.number == selectedContact.number) {
return selectedContact;
} else {
return e;
}
}).toList();
});}
This is expected behavior: gif
Some assumptions I'm making is that (1) each contact has a unique identifier that isn't just a name, and (2) you don't need the selected contacts to be shown in the same list as a search with a different query (if you search H, select Hasan, then search Ha, you expect it to still show up as selected on the next page, but if you search He, Hasan shouldn't shouldn't be on that next list.
The best way to do this is to have one constant list, and one list with results:
Set<String> selectedContactIds = {};
List<Contact> searchedList = [];
void _onChange(bool value, int index) {
final clickedContact = searchedList[index];
bool itemAlreadySelected = selectedContactIds.contains(clickedContact.userID);
setState({
if(itemAlreadySelected) {
selectedContactIds.remove(clickedContact.userID);
} else {
selectedContactIds.add(clickedContact.userID);
}
});
}
Now once you set state, your ListView should be updating the appearance of selected objects by checking if it's selected the same way that the _onChange function is checking if the item was already selected, which was:
bool itemAlreadySelected = selectedContactIds.contains(clickedContact.userID);
And this way, you don't have a whole class for contacts with a dedicated isSelected member. You wouldn't want that anyways because you're only selecting contacts for very specific case by case uses. Hopefully this helps!
Related
I have 3 nested collections (portfolios, rents and leases) and I get the last one like this:
getRentLeases() {
final portfolio = list.where((portfolio) {
return portfolio['documentID'].contains(currentPortfolioId)
? true
: false;
}).toList();
final rent = portfolio[0]['rents'].where((rent) {
return rent['documentID'].contains(currentRentId) ? true : false;
}).toList();
return rent[0]['leases'];
}
as you see I always have the ID for previous collection saved.
To find a specific lease I could follow similar approach:
var lease = getRentLeases().where((lease) {
return lease['documentID'].contains(newLease['documentID'])
? true
: false;
}).toList();
but how could I update it? Something like this doesn't work:
lease = newLease;
? true : false is not necessary as contains already gives you a boolean value.
If you only expect one item to be returned by the where method, then you should go for singleWhere as it directly gives you the item instead of the list. If none of these or multiple items satisfy the condition, you get an exception.
You should separate getRentLeases into two methods. In this case its even better to have methods returning the index of the relevant item.
Please consider that I only used dynamic because I don't have access to the data types. You should instead type the variables.
Code:
int getIndexOfPortFolio(String portfolioId) {
return list
.indexWhere(portfolio => portfolio['documentID'].contains(portfolioId));
}
int getIndexOfRent(dynamic portfolio, String rentId) {
return portfolio['rents']
.indexWhere(rent => rent['documentID'].toString().contains(rentId));
}
int getIndexOfLease(dynamic rent, String leaseId) {
return rent['leases']
.indexWhere(lease => lease['documentID'].toString().contains(leaseId));
}
And then you can update your object like this:
void updateLease(String portfolioId, String rentId, String oldLeaseId, dynamic newLease) {
int portFolioIndex = getIndexOfPortFolio(portfolioId);
var portFolio = list[portFolioIndex];
int rentIndex = getIndexOfRent(portFolio, rentId);
var rent = portFolio["rents"][rentIndex];
int leaseIndex = getIndexOfLease(rent, oldLeaseId);
rent["leases"][leaseIndex] = newLease;
}
Errors are thrown by firstwhere method which can be called on Lists
I have a class named Products that holds objects of product(which is model of how each product is) every product object has its own unique id generated using DateTime.now.toString()
Now here I have 2 paths,
**first: ** if I press the update button on my app i will be updating the product already there in my list which i can find using _productList.firstWhere and it will return me my product without any error
option 2
I add a new product with new id, now i have to check where this id product is already there in my list or not
I am using this logic to check whether the id String is available in my list of products or not
bool hasId(String prod_id) {
late bool result;
_items.firstWhere((element) {
if (element.id == prod_id) {
result = true;
}
if (element.id != prod_id) {
result = false;
}
return result;
});
return result;
}
PROBLEM HERE IS
it throws error when it don't find any object with this test
ERROR IS
The following StateError was thrown while handling a gesture:
Bad state: No element
I WANT TO KNOW THAT IT COULDNT FIND ANY OBJECT WITH THAT ID WITHOUT THROWING AN ERROR
NOTE THAT:
_ITEMS HOLD OBJECTS WHICH HAVE ID, ALSO I WANT TO RETURN STRING
I TRIED, orElse: on firstWhere but it wants an object to be returned which I don't have
_item.firstWhere() will return your item Model not boolean.
So, you may do as the following:
List<Product?> _products = [
Product(id: '123', name: 'P1'),
Product(id: '124', name: 'P2'),
];
bool hasId(String productId) {
bool isExist = false;
Product? product = _products.firstWhere((product) => product?.id == productId, orElse: () => null);
if (product != null) {
isExist = true;
}
return isExist;
}
I'm struggling to get this done, after two days I decided to ask you guys for help.
I'm using Mobx as state management, the issue is related to adding/removing an item to/from the list, e.g. if I retrieve two queries of 5 items each, according to the limit and then remove an item from the first query the first item from the second query is duplicate and if I add a new item, the first item from the second query is hidden. I have also set a scrollListener to the ListView.builder to get the bottom of the list and call for more items.
Thanks in advance,
#override
Stream<QuerySnapshot> teste(DocumentSnapshot lastDoc) {
if (lastDoc == null) {
return firestore.collection('teste')
.orderBy('name')
.limit(5)
.snapshots();
} else {
return firestore.collection('teste')
.orderBy('name')
.limit(5)
.startAfterDocument(lastDoc)
.snapshots();
}
}
#observable
ObservableList<List<TesteModel>> allPagedResults = ObservableList<List<TesteModel>>();
#observable
ObservableList<TesteModel> listTeste = ObservableList<TesteModel>();
#observable
DocumentSnapshot lastDoc;
#observable
bool hasMoreItem;
#action
void teste() {
var _currentRequestIndex = allPagedResults.length;
primaryRepository.teste(lastDoc).listen((query) {
if (query.docs.isNotEmpty) {
var _query = query.docs.map((doc) => TesteModel.fromFirestore(doc))
.toList();
var _pageExists = _currentRequestIndex < allPagedResults.length;
if (_pageExists) allPagedResults[_currentRequestIndex] = _query;
else allPagedResults.add(_query);
listTeste = allPagedResults.fold<List<TesteModel>>(<TesteModel>[],
(initialValue, pageItems) => initialValue..addAll(pageItems)).asObservable();
if (_currentRequestIndex == allPagedResults.length - 1) lastDoc = query.docs.last;
hasMoreItem = _query.length == 5;
}
});
}
Any luck with this issue?
For adding new items and having multiple pages you could do something like this:
if (allPagedResults.contains(allPagedResults[0].last) == false) {
allPagedResults.last.add(allPagedResults[0].last);
allPagedResults.last.remove(allPagedResults[0].first);
}
I want to fetch and show corresponding player's data when their name is selected from the suggestion list. I have AutoCompleteTextField widget using which player's name is typed and selected from the list, as below:
Current issue I am facing is, when player is searched and selected, then I get null value back at first, as below:
But when I tap in the search field and select the same player again from suggestion list, then the api call is made and data is shown properly, as below:
Current code is, on itemSubmitted parameter of AutoCompleteTextfield, I am making call to the method that shows the data inside setState(), as below:
itemSubmitted: (item) {
setState(() {
searchTextField.textField.controller.text = item.name;
textInput = true;
showData();
});
},
Inside showData(), I am parsing the json per input search in the field and if player data is found, I am making another call inside same method to fetch the data, as below:
showData() {
String input = searchTextField.textField.controller.text.toLowerCase();
found = false;
for (var i = 0; i < PlayerViewModel.players.length; i++) {
if (PlayerViewModel.players[i].name.toLowerCase() == input) {
players = PlayerViewModel.players[i];
found = true;
break;
}
}
if (found) {
fetchJson(players.pid);
}
}
void fetchJson(int pid) async {
var response = await http.get(
'http://cricapi.com/api/playerStats?apikey=<apiKey>&pid=$pid');
if (response.statusCode == 200) {
String responseBody = response.body;
var responseJson = jsonDecode(responseBody);
name = responseJson['name'];
playingRole = responseJson['playingRole'];
battingStyle = responseJson['battingStyle'];
country = responseJson['country'];
imageURL = responseJson['imageURL'];
data = responseJson;
The data is fetched properly but not firing at first attempt when I select player from list, but only at second attempt when I select the player again from the list. And this happens for every subsequent player search.
What am I missing here ? How to fetch and show the data as soon as the player name is selected from suggestion list ?
You should implement a FutureBuilder where the future is fetchJson() which returns data. Also try to see what it returns.
if (found) {
fetchJson(players.pid).then(print);
}
hey there i am trying to display all of the options from my database in a dropdown,i have them displaying but i only want one of each result to appear and i cant figure out how to to get ride of the duplicates this is what it looks like when i click on the dropdown
here is the code to pull in the results
void _getFieldsData() {
getUserDetails().then((data) {
final items = jsonDecode(data).cast<Map<String, dynamic>>();
var fieldListData = items.map<User>((json) {
return User.fromJson(json);
}).toSet().toList();
///set list for class
_selectedField = fieldListData[0].series;
_selectedField = fieldListData[0].classs;
setState(() {
for (Map user in items) {
_userDetails.add(User.fromJson(user));
print(_userDetails.length);
//if (_userDetails.classs != userDetail.classs.contains(_selectedText))
}
});
// update widget
setState(() {
_fieldList = fieldListData.toSet().toList();
//print(resultseries);
// print(uniqueCount);
print(_fieldList);
});
});
here is the dropdown
new DropdownButton<String>(
hint: Text("Series"),
// value: null,
items: _fieldList.map((value){
return DropdownMenuItem<String>(
value: value.series,
child: Container(
width: 100,
child: new Text(value.series),
it's not clear exactly what your User class looks like, but im assuming you have multiple fields that do not all have same values, for example, each with a unique id, that's why the following line isn't working in your case:
setState(() {
_fieldList = fieldListData.toSet().toList();
});
i would suggest using List.fold, List.any, and change the line above to check for only .series field, as below:
List initialResultsList = [];
setState(() {
// use fold (similar to .reduce()) to iterate through fieldListData and return the updated filtered 'results'
// list with each iteration:
_fieldList = fieldListData.fold(initialResultsList, (results, currentItem) {
// check to see if currentItem.series already exists in any item of filtered results:
if (!results.any((item) => item.series == currentItem.series)) {
// if not, add it to results:
results.add(currentItem);
}
// return results for next iteration
return results;
});
}