I've been working with flutter-hooks' useFuture method, but I need to use it conditionally (based on user-input in this case). I've found this approach, but I need to know if there is a better way:
Storing what data is selected using useState, and if there is some data selected, change the future from null to the actual future. Example code:
final selected = useState<int>(null);
final future = useMemoizedFuture(() => selected.value != null ? http.get("someapi") : null);
Use the data as the keys of the memoizer. Then add the conditions for working with the future in the code where you need it, not in the hooks themselves.
final selectedIndex = useState<int?>(null);
final someFuture = useMemoizedFuture(() => http.get("someapi"), [useSelected.value]);
Related
In my code I want to increment the usage by the value 1. I have the usage in firebase and I use it in a chart. I use this function to increment by the value 1:
addActivity(
int day,
) async {
final documentSnapshot = await FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid.toString())
.collection('activity')
.doc('${globals.year}')
.collection('week${globals.week}')
.doc(day.toString())
.set({'usage': FieldValue.increment(1)});
}
The problem is that whatever I do the usage value is always set to the value that is in the braces (now 1). I tested this with multiple values and it doesnt seem to work. When I change the initial value to 2 the value after tapping the button isnt 3 but 1...
I'm open and thankful to/for all suggestions :)
That way it's written now using set(), your code always going to overwrite the contents of the document. If you want to update a document, you should use update() instead to update an existing document, or set() with the merge option (SetOptions(merge: true)).
.set({'usage': FieldValue.increment(1)}, SetOptions(merge: true));
To achieve the desired effect you should use update method. set overrides data so it actually sets 1 each time for you. So, just refactor to this
.update({'usage': FieldValue.increment(1)});
I have stored in shared_preferences key value pairs like below....
item_001 = 'some data'
item_103 = 'some data'
item_007 = 'some data'
item_059 = 'some data'
I am trying get all the stored values begins with item_***
I know how to read and write with single key (example below)... but I am trying to get a list of items from shared_preferences where the key name begins with item_.
string
read: final myString = prefs.getString('my_string_key') ?? '';
write: prefs.setString('my_string_key', 'hello');
stringList
read: final myStringList = prefs.getStringList('my_string_list_key') ?? [];
write: prefs.setStringList('my_string_list_key', ['horse', 'cow', 'sheep']);
due to some reason, I don't want to store all the items in one list.... I want to store each item with separate key.
I searched in google and in stackoverflow, unfortunately no where found proper answer....
also I looked into this one, but not understood how to implement partial key search...
esetintis got to this first but I doodled this code so I guess I'll share it. But yes, you have to first get all of the keys in the shared preferences and then get the value for matching keys.
SharedPreferences prefs = await SharedPreferences.getInstance();
Set<String> keys = prefs.getKeys().where((key)=>key.startsWith('item_'));
for (String key in keys) {
String value = prefs.getString(key); // Throws an error if you store something other than a String
// Do your thing
}
I don't think there is a way to make such query. However, you can tackle the issue with some extra steps.
1 step :
Get all keys from SharedPreferences with prefs.getKeys() method. This will return a Set of keys. Now you can assign a List<String> keys = prefs.getKeys().where((k)=>k.startsWith('item_')) which will have the keys you want to get from the Storage.
2nd :
Iterate the filtered array and get the values you want by calling SharedPreferences, and save them to some variable.
Assuming that you have all the keys in a list, for example:
List<String> keys = ['item_001', 'item_103', 'item_007', 'item_059', 'other_key', 'blablabla'];
Now, you could iterate through all of them and checking the ones that starts with "item_", like this:
var itemKeys = [];
for(var i=0;i<keys.length;i++){
if (keys[i].startsWith('item_')) {
itemKeys.add(keys[i]);
}
}
print(itemKeys); // [item_001, item_103, item_007, item_059]
In the above example, itemKeys contains all the needed keys for you. What you could also do is to add the proper logic to fetch values from the shared preferences inside the if statement in the loop:
var result = [];
for(var i=0;i<keys.length;i++){
if (keys[i].startsWith('item_')) {
result.add(prefs.getString(keys[i]) ?? '');
}
}
result should contain what are you looking for.
I am using sembast package for local data storage for a flutter app. When I search through the local data, I want to get the results regardless of whether letters are in caps or small. My current code is sensitive to capital and small letters.
Future searchFoodByField(String fieldName, String searchItem) async {
var finder = Finder(filter: Filter.matches(fieldName, searchItem));
final recordSnapshots = await _foodStore.find(
await _db,
finder: finder,
);
return recordSnapshots.map((snapshot) {
final food = Food.fromMap(snapshot.value);
food.foodId = snapshot.key;
return food;
}).toList();
}
How can it be modified to get the desired outcome?
I'm assuming you want to look for the exact word. For non-english language, you might also want to remove the accent. (diacritic package can help help here).
// Using a regular expression matching the exact word (no case)
var filter = Filter.matchesRegExp(
fieldName, RegExp('^$searchItem\$', caseSensitive: false));
You can also use a custom filter, to perform any filtering you want:
// Using a custom filter exact word (converting everything to lowercase)
searchItem = searchItem.toLowerCase();
filter = Filter.custom((snapshot) {
var value = snapshot[fieldName] as String;
return value?.toLowerCase() == searchItem;
});
I am updating sort & filter models via api:
this.gridApi.setFilterModel(filterModels);
this.gridApi.setSortModel(sortModels);
The problem with this is I have a server request bound to the change even of both sort & filter so when user changes then the data is updated. This means when I change model on code like restoring a state or resetting the filters it causes multiple requests.
Is there a way to update the filter/sort model without triggering the event?
I see there is a ColumnEventType parameter but couldn't see how it works. Can I specify some variable that I can look for inside my event handlers to get them to ignore calls that are not generated from user?
I am trying to manage URL state so when url query params change my code sets the models in the grids but this ends up causing the page to reload multiple times because the onFilter and onSort events get called when the model is set and there is no way I can conceive to prevent this.
At the time, you are going to have to manage this yourself, ie, just before you call the setModel, somehow flag this in a shared part of your app (maybe a global variable)
Then when you react to these events, check the estate of this, to guess where it came from.
Note that at the moment, we have added source to the column events, but they are not yet for the model events, we are planning to add them though, but we have no ETA
Hope this helps
I had to solve similar issue. I found solution which working for my kind of situation. Maybe this help someone.
for (let j = 0; j < orders.length; j++) {
const sortModelEntry = orders[j];
if (typeof sortModelEntry.property === 'string') {
const column: Column = this.gridColumnApi.getColumn(sortModelEntry.property);
if (column && ! column.getColDef().suppressSorting) {
column.setSort(sortModelEntry.direction.toLowerCase());
column.setSortedAt(j);
}
}
this.gridApi.refreshHeader();
Where orders is array of key-value object where key is name of column and value is sorting directive (asc/desc).
Set filter without refresh was complicated
for (let j = 0; j < filters.length; j++) {
const filterModelEntry = filters[j];
if (typeof filterModelEntry.property === 'string') {
const column: Column = this.gridColumnApi.getColumn(filterModelEntry.property);
if (column && ! column.getColDef().suppressFilter) {
const filter: any = this.gridApi.getFilterApi(filterModelEntry.property);
filter['filter'] = filterModelEntry.command;
filter['defaultFilter'] = filterModelEntry.command;
filter['eTypeSelector'].value = filterModelEntry.command;
filter['filterValue'] = filterModelEntry.value;
filter['filterText'] = filterModelEntry.value;
filter['eFilterTextField'].value = filterModelEntry.value;
column.setFilterActive(true);
}
}
}
Attributes in filter:
property - name of column
command - filter action (contains, equals, ...)
value - value used in filter
For anyone else looking for a solution to this issue in Nov 2020, tapping into onFilterModified() might help. This gets called before onFilterChanged() so setting a value here (eg. hasUserManuallyChangedTheFilters = false, etc.) and checking the same in the filter changed event is a possible workaround. Although, I haven't found anything similar for onSortChanged() event, one that gets called before the sorting is applied to the grid.
I am not sure any clean way to achieve this but I noticed that FilterChangedEvent has "afterFloatingFilter = false" only if filterModel was updated from ui.
my workaround is as below
onFilterChanged = event:FilterChangedEvent) => {
if(event.afterFloatingFilter === undefined) return;
console.log("SaveFilterModel")
}
I am looking for a way to conditionally combine Set operations. At the moment I have been unable to increment onto the updatedefinitions without having them consecutively dotted one after the other.
eg. instead of:
Builders<BsonDocument>.Update.Set("someElement.Length", b.Length)
.Set("someElement.Path", b.Path)
I am trying to get find a way to use something in the vain of:
var update = Builders<BsonDocument>.Update;
bool hasChanged = false;
if (a.Length != b.Length)
{
hasChanged = true;
update.Set("someElement.Length", b.Length)
}
if (a.Path != b.Path)
{
hasChanged = true;
update.Set("someElement.Path", b.Path)
}
if (hasChanged)
await someCollection.UpdateOneAsync(Builders<someModel>.Filter.Eq("_id", a.Id), update);
Is there a way of doing this or am I chasing a pie in the sky? I dont want to replace the entire document, and am looking to only update fields that have changed.
UpdateDefinition is an immutable object, so chaining operations on them keeps creating a new one each time. To do it conditionally, you need assign the result back to itself, just like LINQ.
update = update.Set(...);
If you maintain a collection of your conditionally created UpdateDefinitions, you can pass that collection into the Combine operator to create your final UpdateDefinition.
You can find an example on this similar question: C# MongoDB Driver - How to use UpdateDefinitionBuilder?