Multi level Map or List of maps in dart? - flutter

I have a Map of location data like this :
{
'country': 'Japan',
'city': 'Tokyo',
'Latitude': 35.6762,
'Longitude': 139.6503,
'utcOffset': 9
}
witch is the right solution for storing this data and why ?
1) list of maps :
List<Map<String, dynamic>> locations = [
{
'country': 'Japan',
'city': 'Tokyo',
'Latitude': 35.6762,
'Longitude': 139.6503,
'utcOffset': 9
}
];
or multi level data object
var locations = {
{
'country': 'Egypt',
'city': 'Cairo',
'Latitude': 30.033333,
'Longitude': 31.233334,
'utcOffset': 2
},
{
'country': 'Thailand',
'city': 'Bangkok',
'Latitude': 13.7563,
'Longitude': 100.5018,
'utcOffset': 7
},
};
And how to access the data in the 2 cases?

I usually use Map when I want to have key-value pairs because I can directly get the value by key. As a example if you have Map of employes and if you keep the employee id as a key, you can easily access it. Specially for search, or for drop down this is elegant.
Most of the time if I can avoid Map and I will use List because it is very easy for me to handle large amount of data(In flutter I can throw a list to a ListView easily).
But both have there own greatness for depending on the scenario. And note that Map cannot have duplicate key, where List doesnt have like that limitation(Ex: Map can use to avoid duplicate).
var locations = {
{
'country': 'Egypt',
'city': 'Cairo',
'Latitude': 30.033333,
'Longitude': 31.233334,
'utcOffset': 2
},
{
'country': 'Thailand',
'city': 'Bangkok',
'Latitude': 13.7563,
'Longitude': 100.5018,
'utcOffset': 7
},
};
for (var element in locations) {
print(element["country"]);
}
// or
locations.forEach((element) => print(element["country"]));
Here what doc contains about this:
Map<K, V> class
A collection of key/value pairs, from which you retrieve a value using
its associated key.
There is a finite number of keys in the map, and each key has exactly
one value associated with it.
Maps, and their keys and values, can be iterated. The order of
iteration is defined by the individual type of map.
List<E> class
An indexable collection of objects with a length.
Subclasses of this class implement different kinds of lists. The most
common kinds of lists are:
Fixed-length list. An error occurs when attempting to use operations that can change the length of the list.
Growable list. Full implementation of the API defined in this class.
The default growable list, as returned by new List() or [], keeps an
internal buffer, and grows that buffer when necessary. This guarantees
that a sequence of add operations will each execute in amortized
constant time. Setting the length directly may take time proportional
to the new length, and may change the internal capacity so that a
following add operation will need to immediately increase the buffer
capacity. Other list implementations may have different performance
behavior.

You can use both but the first one is preferred as its easier to loop through the items and get the data out.
Not that you can't loop through the second data set, just that its more tedious to loop an array.
Given that your array of objects look like this:
List<Map<String, dynamic>> locations = [
{
'country': 'Japan',
'city': 'Tokyo',
'Latitude': 35.6762,
'Longitude': 139.6503,
'utcOffset': 9
}
];
Suppose you want to take out all the country names and store them in a separate array, It would be really easy and the code should look as follows:
List<String> countryNames = [];
locations.forEach((location) => countryNames.add(location['country]));

You can get data dynamically from a Map creating a this function getData
dynamic getData(Map data, List<String> way) {
dynamic dataTemp = data;
if (way.length > 0) {
for (int x=0; x < way.length; x++) {
dataTemp = dataTemp[way[x]];
}
}
return dataTemp;
}
List<Map<String, dynamic>> locations = [
{
'country': 'Japan',
'city': 'Tokyo',
'Latitude': 35.6762,
'Longitude': 139.6503,
'utcOffset': 9,
'example' : {
'data' : "text",
'number' : 20,
'boolean': false
}
}
];
getData(locations[0],["example","number"]);
output: 20

Related

Flutter FireStore Create, Read and Update Array with a Map Object

Fairly new to Flutter and FireStore, I'm having trouble finding examples or explanations on how to add Maps to an Array (specifically, Creating the initial Array with a Map, then Reading then Adding more Maps to the collection of the Notes Array).
I have an Event Class for scheduling events and that is working very well. What I'm trying to add is a "Notes" field that would allow an some Administrator to add Notes on one day then come back and add to that Event another set of notes as things change. I don't want to "overwrite" the previous Notes, just keep adding more Notes to the Array of notes.
Specific questions:
How do I Create the "Array" when adding the entry into FireStore using Flutter?
How do I Read the data in the "Array" when it's coming back as a set of "Map fields" in Flutter?
How do I just Add to the Array with a new Note? (I think this needs FieldValue.arrayUnion([someMap]) but I'm not certain as this appears to avoid any overwriting.
The page section here shows writing it out but little else: https://firebase.google.com/docs/firestore/manage-data/add-data#data_types
Below is an example of the FireStore structure I'm trying to create.
Has anyone done this before and do you have some direction you can provide? The Firebase documentation in this space is thin...
To create an new document with array in firestore:
FirebaseFirestore.instance.collection('path/to/collection').add(
{ 'notesArray': [
{'date': 'sometime', 'notes': 'somenotes', 'user': 'someuser'}
]},
SetOptions(merge: true),
);
// or
FirebaseFirestore.instance.collection('path/to/collection').add(
{ 'notesArray': []}, // for an empty array
SetOptions(merge: true),
);
To read the data:
FirebaseFirestore.instance.doc('company').get().then((value) {
final doc = value.data()!;
print(doc['lname']); // should print Gethsemane
print(doc['notesArray'] as List); // should print notes array
final notes = doc['notesArray'] as List;
for (final note in notes) {
print(note['date']);
print(note['notes']);
print(note['user']);
}
// or
for (int i = 0; i < notes.length; i++) {
print(notes[i]['date']);
}
});
Add new data to notes array. Simply use FieldValue.arrayUnion.
E.g
FirebaseFirestore.instance.doc('path/to/doc').set(
{
'notesArray': FieldValue.arrayUnion([
{'date': 'sometime', 'notes': 'somenotes', 'user': 'someuser'}
]),
},
SetOptions(merge: true),
);
// I used set method along with setOptions in order not to
// override other fields (e.g modifiedBy field)
Keep in mind that if your array is going to be very large, it is better to store the notes as a subcollection.
##EDIT##
If you want to update a nested array, you can use the dot notation.
class SchedulerEvents {
final List<Map<String, dynamic>>? notes;
SchedulerEvents({required this.notes});
}
// lets assume we have data like this that we want to update
final data = SchedulerEvents(
notes: [
{'date': 'sometime', 'notes': 'somenotes', 'user': 'someuser'},
],
);
FirebaseFirestore.instance.doc('path/to/doc').set(
{
'schedulerEvents.notes': FieldValue.arrayUnion([data.notes![0]]),
// or
'schedulerEvents.notes': FieldValue.arrayUnion(data.notes![0]),
// 'schedulerEvents.lname: 'test', // to update other fields
},
SetOptions(merge: true),
);
// we used dot notation to update fields here.

Casting DatabaseEvent snapshot.value to a specific type in Flutter

I'm trying to retrieve data from a DatabaseEvent event type.
The data I have on the database is this
in this layout
Basically some users id and each user has a a different number of menu orders.
What I want to do is retrieve each order of each user and do something with it.
Right now I'm using
Map data = event.snapshot.value as Map;
but since it's a dynamic cast, it sometimes retrieves the values as a List of Objects and sometimes as a Map of Objects..
For example if the orders number are in order as the picture above (order number 1 and number 2), it retrieves the data like this :
{ZqsrXaqhXbPFgPTvIbnxRNw3bX42: [null, {number: 1, quantity: 3}], tzqjzgzy: [null, {number: 1, quantity: 2}, {number: 2, quantity: 3}]}
as a List and it retrieves a null because there is no order 0. If I change the orders from 1 and 2 to 2 and 3, it gives me the same result but with two nulls at the beginning.
If I change the orders number to more random numbers, it retrieves them correctly as a Map, or more specifically as an _InternalLinkedHashMap<Object?, Object?>.
{ZqsrXaqhXbPFgPTvIbnxRNw3bX42: {12: {number: 12, quantity: 2}, 24: {number: 24, quantity: 3}}}
I want to retrieve it everytime in the same way so I tried casting the snapshot.value in different ways like so
Map<dynamic, Map> data =
event.snapshot.value as Map<dynamic, Map>;
but everytime I do I don't get any data.
This is my code right now
.then((DatabaseEvent event) {
if (event.snapshot.exists) {
Map<dynamic, dynamic> data =
event.snapshot.value as Map;
//sum all orders
Map<int, Map> allOrders = {};
data.forEach((key, userOrders) {
userOrders.forEach((key, order) {
allOrders[order['number']] = {
'number': order['number'],
'quantity': allOrders[order['number']] != null
? allOrders[order['number']]!['quantity'] +
order['quantity']
: 0 + order['quantity'],
};
});
});
return allOrders;
} else {
throw Exception('Error snapshot does not exist');
}
});

Filter and manipulate a map

It's the first time I use Dart and I'm stuck with a simple thing.
I have a simple Map and I need to remove some items from this map and modify the content.
I have this:
Map<String, List<String>> dataset = {
'apple': ['apple1', 'apple2', 'apple3'],
'pear': ['pear1', 'pear2'],
'ananas': ['ananas1', 'ananas2', 'ananas3'],
'orange': ['orange1', 'orange2', 'orange3', 'orange4'],
};
List<Map<dynamic, String>> fruits = [
{'key': 'pear', 'labelToShow': 'Pear fruit'},
{'key': 'ananas', 'labelToShow': 'My ananas'},
];
and I would like to have this:
Map<String, Map<String, List<String>>> result = {
'pear': {
'values': ['pear1', 'pear2'],
'labelToShow': 'Pear fruit'
},
'ananas': {
'values': ['ananas1', 'ananas2', 'ananas3'],
'labelToShow': 'My ananas'
},
};
So, basically, I need to remove from dataset the items that have the key that it's not included in fruits (in field key) and then I need to add the field labelToShow.
I dont' know ho to do that.
I started removing items from dataset doing so:
dataset.removeWhere((k, v) => k != 'pear' && k != 'ananas');
but I don't like, I would like to loop through fruits.
Can someone please help me?
Thanks a lot
I wouldn't remove anything from dataset. Instead I'd build a new map from scratch, with just the data you want.
How about:
Map<String, Map<String, List<String>>> result = {
for (var fruit in fruits)
fruit["key"]: {
"values": dataset[fruit["key"]],
"labelToShow": fruit["labelToShow"]
}
};

replace characters in string if they matched specific chars in FLUTTER

I am creating a POS where i want to show the items purchased as a text and i intent to store those as a text too. I am separating each item by '\n' character.
Now the problem is that i dont know how to check for text which specifically matches a certain product. I have tried using string.contains method but it is not what i desire.
For example: I have products names like:
Chicken
Chicken Cheese
Now these are two different products but every chicken cheese is going to contain chicken too in it.
So, How can i replace the product
Products are in a list which are further part of a map.
However, for this question; you can assume that all the items are stored as a list, Example below:
['chicken', 120],
['chicken Cheeze', 150],
['chicken Cheeze Pizza', 180],
.......................
Than, down in the code i am saving those products as a string (when tapped upon.).
// Gesture Detector is inside a Galleryview which is further part of a row and expanded widgets,
GestureDetector(
onTap: () {
//bill items is a string Storing all products.
billItems += (item[0] +
' : ' +
item[1].toString() + '\n');
},
P.s: All i want to do is to do something like: "chicken * 3" if chickens are orders 3 times instead of printing it 3 times. Thanks
So, I have came up with a solution, though I don't know how optimal it is or fast.
I first create a product model to serialize it :
class Product {
String name;
double price;
int count;
Product({required this.name, required this.price,this.count=0});
factory Product.fromDynamic(List product) =>
Product(name: product[0], price: product[1]);
Map<String, dynamic> toJson() {
return {"name": name, "price": price,"count":count,};
}
}
So assuming our products are in this form :
final originalProducts = [
['chicken', 120],
['chicken', 135],
['chicken', 140],
['chicken Cheeze', 150],
['chicken Cheeze Pizza', 180],
];
I created a List<Product> :
List<Product> modeledProducts =
List.generate(originalProducts.length, (i) => Product.fromDynamic(originalProducts[i]));
Next I create a temporary List<String> where I will save any duplicate products found (their names), and I create the final List<Products> which will have unique products with their prices summed up :
List<String> duplicates = [];
List<Product> finalProducts = [];
Then we iterate through the modeledProducts to find the duplicates and do the necessary operations :
for (final product in modeledProducts) {
if (!duplicates.contains(product.name)) {
duplicates.add(product.name);
finalProducts.add(product);
} else {
finalProducts.firstWhere((p) => p.name == product.name).price +=
product.price;
finalProducts.firstWhere((p) => p.name == product.name).count++;
}
}
And now you are ready to use your data :
for (final fProduct in finalProducts) {
print(fProduct.toJson());
}
The above will print :
{name: chicken, price: 395, count: 3}
{name: chicken Cheeze, price: 150, count: 1}
{name: chicken Cheeze Pizza, price: 180, count: 1}

Hello there, how to check which lists in a class have expired time element in them?

In a class I have lists, in those lists I have maps, each map has 'interval' and 'time' element, I want to check which of those 'time' elements have expired and then pass those lists to further sorting according to interval (this sorting thing is working already), just not sure how to check which lists have expired 'time' in them?
In the code I have marked with //TODO part that is missing
Thank you
class QuizBrain {
List<Map<String, dynamic>> products = [
{
'id': 24,
'interval': 10000,
'time': '2021-12-14 20:37:21.190473',
},
{
'id': 36,
'interval': 20000,
'time': '2020-11-14 20:37:21.190453',
},
{
'id': 48,
'interval': 30000,
'time': '2020-12-14 20:37:21.190453',
},
];
min() {
//TODO need to set condition to check which lists 'time' is expired, then i pass those that 'time is expired to further sorting beneath
if (products != null && products.isNotEmpty) {
products.sort((a, b) => a['interval'].compareTo(b['interval']));
//print(products);
return (products.first['id']);
}
}
}
You should be able to convert the time attributes into DateTime objects and compare them to now using compareTo:
final now = DateTime.now();
final expired = products.where((item) {
final itemTime = DateTime.parse(item["time"]);
return itemTime.compareTo(now) < 0;
});
Then if I've read your question correctly you just need to replace products with expired in your if statement.
This is from the docs of compareTo():
this.compareTo(other)
Compares this DateTime object to [other], returning zero if the values are equal.
Returns a negative value if this DateTime [isBefore] [other].
It returns 0 if it [isAtSameMomentAs] [other], and returns a positive value otherwise (when this [isAfter] [other]).