How to store a simple matrix in Cloud Firestore using Dart/Flutter? - flutter

Let's say I have the following integer matrix in Dart:
final List<List<int>> myMatrix = [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
];
Then I try to store it in Cloud Firestore like this:
await Firestore.instance.collection("anyCollection").add({
"matrix" : myMatrix,
});
But as soon as I run the code above, the simulator crashes and I get the following error:
*** First throw call stack:
(
0 CoreFoundation 0x00007fff23c4f02e __exceptionPreprocess + 350
1 libobjc.A.dylib 0x00007fff50b97b20 objc_exception_throw + 48
2 Runner 0x000000010e83f8b5 _ZN8firebase9firestore4util16ObjcThrowHandlerENS1_13ExceptionTypeEPKcS4_iRKNSt3__112basic_stringIcNS5_11char_traitsIcEENS5_9allocatorIcEEEE + 581
3 Runner 0x000000010e83edf3 _ZN8firebase9firestore4util5ThrowENS1_13ExceptionTypeEPKcS4_iRKNSt3__112basic_stringIcNS5_11char_traitsIcEENS5_9allocatorIcEEEE + 67
4 Runner 0x000000010e890589 _ZN8firebase9firestore4util20ThrowInvalidArgumentIJEEEvPKcDpRKT_ + 57
5 Runner 0x000000010e959f65 -[FSTUserDataConverter parseData:context:] + 1045
6 Runner 0x000000010e<…>
Lost connection to device.
I am running cloud_firestore: ^0.13.0+1 (currently the latest version) in the pubspec.yaml file. Everything is fine when running the flutter doctor command as well.
What am I doing wrong? Is it even possible to store matrixes in Firestore? If not, can I store that data using another logic?

While you can store an array in Firestore, as mentioned in the documentation, you are not allowed to store another array inside it.
You could take an approach where you store the arrays as fields in a document so you can actually keep each array separated. This is shown over at this thread, or this one.
Hope you find this useful!

The following approach will work for the Matrix you gave as an example, but you won't be able to query the matrix directly from Firestore. You must take the whole matrix out, parse it, and then use it:
final List<List<int>> myMatrix = [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
];
// Encode your matrix into a JSON String to add to Firestore
String jsonMatrix = jsonEncode(myMatrix);
// Decode your Json String into a JSON object
var decodedMatrix = jsonDecode(jsonMatrix);
// Decode JSON object back into your matrix
List<List<int>> newList = List<List<int>>.from(decodedMatrix.map((row){
return List<int>.from(row.map((value) => int.parse(value.toString())));
}));
// Print to show that the new object is of the same type as the original Matrix
print(newList is List<List<int>>);

I ended up making the following helper that converts the matrix into a map that's compatible with Firestore:
class MatrixHelper {
// Creates a map that can be stored in Firebase from an int matrix.
static Map<String, dynamic> mapFromIntMatrix(List<List<int>> intMatrix) {
Map<String, Map<String, dynamic>> map = {};
int index = 0;
for (List<int> row in intMatrix) {
map.addEntries([MapEntry(index.toString(), {})]);
for (int value in row) {
map[index.toString()].addEntries(
[MapEntry(value.toString(), true)]
);
}
index += 1;
}
return map;
}
// Creates an int matrix from a dynamic map.
static List<List<int>> intMatrixFromMap(Map<dynamic, dynamic> dynamicMap) {
final map = Map<String, dynamic>.from(dynamicMap);
List<List<int>> matrix = [];
map.forEach((stringIndex, value) {
Map<String, dynamic> rowMap = Map<String, dynamic>.from(value);
List<int> row = [];
rowMap.forEach((stringNumber, boolean) {
row.add(int.parse(stringNumber));
});
matrix.add(row);
});
return matrix;
}
}
It's really simple to use, to save in Firestore it's like this:
final List<List<int>> myMatrix = [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
];
await Firestore.instance.collection("anyCollection").add({
"matrix" : MatrixHelper.mapFromIntMatrix(myMatrix),
});
To load the matrix from Firestore, it's like this:
// Loads the DocumentSnapshot:
final document = await Firestore.instance.collection("anyCollection").document("anyDocument").get();
// Retrieves the Matrix:
final List<List<int>> matrix = MatrixHelper.intMatrixFromMap(document.data["matrix"]);

Related

Dart - For loop is changing elements of my list even when it is cloned

When I access to the elements of my list in a for loop, I would like to be able to modify them without impacting the original list.
Here's a simple example :
List pairs = [
[1,8],
[1,6],
];
print(pairs);
List copy = List.from(pairs);
for (List pair in copy) {
if(pair.contains(1)) {
pair.remove(1);
}
}
print(pairs);
The output of this is :
[[1, 8], [1, 6]]
[[8], [6]]
I expected the output to be :
[[1, 8], [1, 6]]
[[1, 8], [1, 6]]
I tried to replace List copy = List.from(pairs); with :
List copy = [...pairs]; // This
List copy = []..addAll(pairs); // Or this
Nothing is working.
The only solution I found was to do this :
List temp = List.from(pair);
if(temp.contains(1)) {
temp.remove(1);
}
But it seems to be a bit overkill. Does anyone has another idea ?
As jamesdlin says, using List.from or the spread operator just creates a shallow copy of the list. Dart does not have a built-in deep copy function that I could find, but if you'll only be working with nested lists like this we can define our own pretty easily:
List<T> deepCopy<T>(List<T> list) =>
list.map((e) => e is List ? deepCopy(e) : e).cast<T>().toList();
Here's a dartpad showing the result.

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

Sort a map within a map and convert to list within a list

Here's my data in Firebase:
prices
price1
priceAmount1: 10
priceAmount2: 20
priceAmount3: 30
price2
priceAmount1: 15
priceAmount2: 5
priceAmount3: 7
price3
priceAmount1: 2
priceAmount2: 4
priceAmount3: 6
If I straight away retrieve it. Sometimes the ordering is price2, price3, price1. So I tried sorting it like this:
var sortedKeys = prices!.price!.keys.toList()..sort();
for (var it = 0; it < sortedKeys.length; it++) {
print('${sortedKeys[it]}');
}
But since it becomes a list, I'm not able to access the map anymore. I wanted to sort the map within a map then store it in a list within a list. Please help. Thanks!
Edit:
final splayByPrice =
SplayTreeMap<dynamic, dynamic>((a, b) => a.compareTo(b));
List<Map<dynamic, dynamic>> priceAmountMap = [
{
'priceAmount1': 10,
'priceAmount2': 20,
'priceAmount3': 30,
},
{
'priceAmount1': 15,
'priceAmount2': 5,
'priceAmount3': 7,
},
{
'priceAmount1': 2,
'priceAmount2': 4,
'priceAmount3': 6,
}
];
Map<dynamic, Map<dynamic, dynamic>> priceMap = {
'price2': priceAmountMap[1],
'price1': priceAmountMap[0],
'price3': priceAmountMap[2],
};
priceMap.forEach((key, value) {
splayByPrice.addAll(value);
});
priceMap.forEach((key, value) {
print('$key \t $value');
});
What it prints, is still not sorted.
price2 {priceAmount1: 15, priceAmount2: 5, priceAmount3: 7}
price1 {priceAmount1: 10, priceAmount2: 20, priceAmount3: 30}
price3 {priceAmount1: 2, priceAmount2: 4, priceAmount3: 6}
Dart's default Map implementation is a LinkedHashMap that stores entries in order of insertion.
(You haven't provided code that clearly shows your data structure, so I'll use my own example.)
If you want the Map to be sorted by the keys' alphabetic order, then you could:
Create a new Map, adding items to it in the desired order:
var theMap = {
4: 'four',
7: 'seven',
6: 'six',
9: 'nine',
5: 'five',
1: 'one',
8: 'eight',
0: 'zero',
3: 'three',
2: 'two',
};
void main() {
var sortedKeys = theMap.keys.toList()..sort();
var sortedMap = {
for (var key in sortedKeys)
key: theMap[key]!,
};
print(sortedMap);
}
Update the existing Map, removing and re-adding items in the desired order:
void main() {
var sortedKeys = theMap.keys.toList()..sort();
for (var key in sortedKeys) {
var value = theMap[key]!;
theMap.remove(key);
theMap[key] = value;
}
print(theMap);
}
Use a SplayTreeMap, which keeps entries sorted by a comparison rule.
import 'dart:collection';
void main() {
var splayTreeMap = SplayTreeMap.of(theMap);
print(splayTreeMap);
}
If you need to sort the data only once, I'd use one of the first two methods. If you're frequently adding new entries, then I'd use a SplayTreeMap instead of a Map/LinkedHashMap so that entries are kept in a sorted order. (But note that lookups, insertions, and removals in a SplayTreeMap are O(log n) instead of O(1).)
Also, if you're sorting strings that contain numbers, you probably don't want to use the normal string sort.

How to get the length for each list according to its key inside map

how to get the length for each list according to its key
Map mymap= <String, List>;
Example
key1 : 5(length of the value(list))
key2 : 48
It seems similar to this,
you can do mymap['k1']?.length, here ?. means it will return null if there is no value.
Rest you can follow #zabaykal's answer.
Map<String, List> mymap = {
"k1": [1, 2, 4],
"k2": [5, 6, 7],
"k3": []
};
print(mymap['k1']?.length);
mymap.forEach((key, value) {
print('$key: ${value.length}');
});
If you want to create a second map with the original keys and the respective lengths as the value you can use the following code where initialMap is the original map with List<T> as values:
final mapListCount = initialMap.map((key, value) => MapEntry(key, value?.length));

react native insertion of array values using react-native-sqlite-storage

i am trying to insert array values into some table for sqlite. my arrays values are not inserted. i am strucked here that was i tried can you guide me in correct way here i am getting the response from axion json output.
response.zonedetails is a array.
for (var i = 0; i < response.zoneDetails.length; i++){
console.log('hi i am ');
console.log('for loop log',response.zoneDetails[i]);
var zoneNmae = response.zoneDetails[i].zonename;
var parentZoneId = response.zoneDetails[i].parentzoneid;
var levelId = response.zoneDetails[i].levelid;
var zoneId = response.zoneDetails[i].zoneid;
await this.setState({zonename: zoneNmae});
await this.setState({zoneId:zoneId});
await this.setState({parentzoneid: parentZoneId });
await this.setState({levelid:levelId});
this.initZonesDB();
//this.addZonesdetailsInfo(response.zoneDetails);
this.addZonesdetailsInfo();
this is my response :"zoneDetails": [
{
"zonename": "zone1",
"zoneid": 1,
"parentzoneid": 10,
"levelid": "2"
},
{
"zonename": "zone2",
"zoneid": 2,
"parentzoneid": 10,
"levelid": "2"
},
i am unable to insert all values every time,it was inserting first array of 1st index values only [0]. can any one suggest me to insert all values in good way for react native.