Flutter: find item in list of maps and update it - flutter

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

Related

Flutter: filter a List<Map<String, dynamic>> to only contain certain items

I have a list of maps where one of the fields in the map is a boolean called "completed". I want to be able to filter the map so that I only get the items for which "completed" is true or false. My current code is the following:
try {
String? email = FirebaseAuth.instance.currentUser!.email;
for (int i = startingIndex; i < endingIndex; ++i) {
var doc = await FirebaseFirestore.instance
.collection(email!)
.doc("goals")
.collection(Utils.goalsCategoriesDropdownItems[i])
.get();
if (doc.size > 0) {
allData.addAll(doc.docs.map((doc) => doc.data()).toList());
}
}
allData.sort(((a, b) {
Timestamp one = a["dueDate"];
Timestamp two = b["dueDate"];
return one.compareTo(two);
}));
return allData;
}
but I don't want to return allData, I want to filter and see if all the items are true or false, so I want something like this:
if (completed == true) {
allData.map((e) => e["completed"] == true);
} else {
allData.map((e) => e["completed"] == false);
}
return allData;
It is not entirely clear what your expected outcome is, but here are two possibilities:
a) If you want to check if every Map in your List has the key completed with a value of true you can use every:
return allData.every((e) => e["completed"] === true)
b) If you want to have a list containing only Maps which contain the key completed with a value of true you can use where:
return allData.where((e) => e["completed"] === true)

Is this approach is an Anti-Pattern? - Flutter

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!

Bad state: no element, I want to fetch object from my class file

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

Cleanest way to implement multiple parameters filters in a REST API

I am currently implementing a RESTFUL API that provides endpoints to interface with a database .
I want to implement filtering in my API , but I need to provide an endpoint that can provide a way to apply filtering on a table using all the table's columns.
I've found some patterns such as :
GET /api/ressource?param1=value1,param2=value2...paramN=valueN
param1,param2...param N being my table columns and the values.
I've also found another pattern that consists of send a JSON object that represents the query .
To filter on a field, simply add that field and its value to the query :
GET /app/items
{
"items": [
{
"param1": "value1",
"param2": "value",
"param N": "value N"
}
]
}
I'm looking for the best practice to achieve this .
I'm using EF Core with ASP.NET Core for implementing this.
Firstly be cautious about filtering on everything/anything. Base the available filters on what users will need and expand from that depending on demand. Less code to write, less complexity, fewer indexes needed on the DB side, better performance.
That said, the approach I use for pages that have a significant number of filters is to use an enumeration server side where my criteria fields are passed back their enumeration value (number) to provide on the request. So a filter field would comprise of a name, default or applicable values, and an enumeration value to use when passing an entered or selected value back to the search. The requesting code creates a JSON object with the applied filters and Base64's it to send in the request:
I.e.
{
p1: "Jake",
p2: "8"
}
The query string looks like:
.../api/customer/search?filters=XHgde0023GRw....
On the server side I extract the Base64 then parse it as a Dictionary<string,string> to feed to the filter parsing. For example given that the criteria was for searching for a child using name and age:
// this is the search filter keys, these (int) values are passed to the search client for each filter field.
public enum FilterKeys
{
None = 0,
Name,
Age,
ParentName
}
public JsonResult Search(string filters)
{
string filterJson = Encoding.UTF8.GetString(Convert.FromBase64String(filters));
var filterData = JsonConvert.DeserializeObject<Dictionary<string, string>>(filterJson);
using (var context = new TestDbContext())
{
var query = context.Children.AsQueryable();
foreach (var filter in filterData)
query = filterChildren(query, filter.Key, filter.Value);
var results = query.ToList(); //example fetch.
// TODO: Get the results, package up view models, and return...
}
}
private IQueryable<Child> filterChildren(IQueryable<Child> query, string key, string value)
{
var filterKey = parseFilterKey(key);
if (filterKey == FilterKeys.None)
return query;
switch (filterKey)
{
case FilterKeys.Name:
query = query.Where(x => x.Name == value);
break;
case FilterKeys.Age:
DateTime birthDateStart = DateTime.Today.AddYears((int.Parse(value) + 1) * -1);
DateTime birthDateEnd = birthDateStart.AddYears(1);
query = query.Where(x => x.BirthDate <= birthDateEnd && x.BirthDate >= birthDateStart);
break;
}
return query;
}
private FilterKeys parseFilterKey(string key)
{
FilterKeys filterKey = FilterKeys.None;
Enum.TryParse(key.Substring(1), out filterKey);
return filterKey;
}
You can use strings and constants to avoid the enum parsing, however I find enums are readable and keep the sent payload a little more compact. The above is a simplified example and obviously needs error checking. The implementation code for complex filter conditions such as the age to birth date above would better be suited as a separate method, but it should give you some ideas. You can search for children by name, and/or age, and/or parent's name for example.
I have invented and found it useful to combine a few filters into one type for example CommonFilters and make this type parseable from string:
[TypeConverter(typeof(CommonFiltersTypeConverter))]
public class CommonFilters
{
public PageOptions PageOptions { get; set; }
public Range<decimal> Amount { get; set; }
//... other filters
[JsonIgnore]
public bool HasAny => Amount.HasValue || PageOptions!=null;
public static bool TryParse(string str, out CommonFilters result)
{
result = new CommonFilters();
if (string.IsNullOrEmpty(str))
return false;
var parts = str.Split(new[] { ' ', ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var part in parts)
{
if (part.StartsWith("amount:") && Range<decimal>.TryParse(part.Substring(7), out Range<decimal> amount))
{
result.Amount = amount;
continue;
}
if (part.StartsWith("page-options:") && PageOptions.TryParse(part.Substring(13), out PageOptions pageOptions))
{
result.PageOptions = pageOptions;
continue;
}
//etc.
}
return result.HasAny;
}
public static implicit operator CommonFilters(string str)
{
if (TryParse(str, out CommonFilters res))
return res;
return null;
}
}
public class CommonFiltersTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
if (value is string str)
{
if (CommonFilters.TryParse(str, out CommonFilters obj))
{
return obj;
}
}
return base.ConvertFrom(context, culture, value);
}
}
the request looks like this:
public class GetOrdersRequest
{
[DefaultValue("page-options:50;amount:0.001-1000;min-qty:10")]
public CommonFilters Filters { get; set; }
//...other stuff
}
In this way you reduce the number of input request parameters, especially when some queries don't care about all filters
If you use swagger map this type as string:
c.MapTypeAsString<CommonFilters>();
public static void MapTypeAsString<T>(this SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.MapType(typeof(T), () => new OpenApiSchema(){Type = "string"});
}

MongoDB C# Combining Fields

The Plan:
So now what I basically want is to take my propertys out of the class, let the user pick some and then pull a List with ONLY those propertys out of MongoDB.
The Code:
here is where the method starts:
private void DoStuffExecute(object obj)
{
Class class= new Class();
ExtractClass(class);
if (propList != null)
{
var result = classService.DoStuff(propList);
}
}
in "ExtractClass()" the Propertys are being pulled out of the Class.
void ExtractClass(object obj)
{
foreach (var item in obj.GetType().GetProperties())
{
propList.Add(item.Name);
}
}
and finally in "classService.DoStuff()" i try to set the "fields".
public List<class> DoStuff(List<string> Props)
{
try
{
var filter = Builders<class>.Filter.Empty;
var fields = Builders<class>.Projection.Include(x => x.ID);
foreach (var item in Props)
{
string str = "x.";
str += item.ToString();
fields = Builders<class>.Projection.Include(x => str);
fields = Builders<class>.Projection.Include(x => item);
}
var result = MongoConnectionHandler.MongoCollection.Find(filter).Project<class>(fields).ToList();
return result;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
var result = new List<class>();
return result;
}
}
when i run the programm it gives me an "Unable to determine the serialization information for x=> value"... since im giving it a string.
The Question:
Does anyone have an Idea how to repair the code above or even make the plan work in another way?
thank you.
First of all: you are using such code lines as : var filter = Builders<class>.Filter.Empty; It is not possible, because class is a reserved keyword in c# (https://msdn.microsoft.com/en-us/library/x53a06bb.aspx) I assume, it's your Model, and i will speak about it as about Model class.
Include Filter needs Expression as a parameter, not a string, you should construct is as a expression. That's the second thing. Third, you should combine your includes as a chain, So your part of creating Include Filter from string List should look like:
var filter = Builders<Model>.Filter.Empty;
var fields = Builders<Model>.Projection.Include(x => x.Id);
foreach (var item in Props)
{
var par = Expression.Parameter(typeof(Model));
var prop = Expression.Property(par, item);
var cast = Expression.Convert(prop, typeof(object));
var lambda = Expression.Lambda(cast, par);
fields = fields.Include((Expression<Func<Model, object>>)lambda);
}
I have all expresiions separate for better understanding: first you create Parameter (x=>), than you add property (x=>x.Property1), than you should cast it to object, and after all create Lambda Expression from it.
And now the last part: You don't need all of it, Include function could get jsut a string as a parameter. So you could instead of all expression call write this:
fields = fields.Include(item);