Append element to a list flutter firebase - firebase

I'm currently working with FLutter and firebase. I'm trying to upload some data to the cloud firestore. So far so good. I have a problem when I'd like to append to a field a list.
As mentioned before this field contains a list of values defined as below:
Map<String, Object> toDocument() {
Map customerAddress = Map();
Map orderCheckoutDetails = Map();
final DateTime now = DateTime.now();
final DateFormat formatter = DateFormat('dd-MM-yyyy');
final String formatted = formatter.format(now);
customerAddress['address'] = this.address;
customerAddress['city'] = this.city;
customerAddress['state'] = this.state;
customerAddress['zipCode'] = this.zipCode;
orderCheckoutDetails['checkoutOrderDate'] = formatted;
orderCheckoutDetails['customerAddress'] = customerAddress;
orderCheckoutDetails['customerName'] = '${this.nome!} ${this.cognome!}';
orderCheckoutDetails['customerPhone'] = this.numeroTelefono!;
orderCheckoutDetails['products'] = this.products!.map((product) => product.name).toList();
orderCheckoutDetails['subtotal'] = this.subtotal!;
orderCheckoutDetails['deliveryFee'] = this.deliveryFee!;
orderCheckoutDetails['total'] = this.total!;
List<Map<dynamic, dynamic>> orderList = [orderCheckoutDetails];
return {
'orderCheckoutDetails': orderList,
};
}
This is how it shows me the item on Firebase (which is correct).
enter image description here
This is how I upload the document to Firebase.
#override
Future<void> addCheckout(Checkout checkout) async {
print(checkout.email!);
final docExists = await _checkIfUserCheckoutExists(checkout.email!);
print(docExists);
if (!docExists) {
return _checkoutCollection
.doc(checkout.email!)
.set(checkout.toDocument());
} else {
await _checkoutCollection.doc(checkout.email!).update({
"orderCheckoutDetails":
FieldValue.arrayUnion(checkout.toDocument() as List)
});
}
}
What I'd like to do is to append at the end of the document another element (the checkout element passed by parameter). How can I do that?

Your can use set with merge option in both cases (whether the document exists or not), it will create or update the document as needed. Plus your toDocument method should return orderList itself, not the map you are currently returning.
Try this:
Map<dynamic, dynamic> toDocument() {
Map customerAddress = Map();
Map<dynamic, dynamic> orderCheckoutDetails = Map();
final DateTime now = DateTime.now();
final DateFormat formatter = DateFormat('dd-MM-yyyy');
final String formatted = formatter.format(now);
customerAddress['address'] = this.address;
customerAddress['city'] = this.city;
customerAddress['state'] = this.state;
customerAddress['zipCode'] = this.zipCode;
orderCheckoutDetails['checkoutOrderDate'] = formatted;
orderCheckoutDetails['customerAddress'] = customerAddress;
orderCheckoutDetails['customerName'] = '${this.nome!} ${this.cognome!}';
orderCheckoutDetails['customerPhone'] = this.numeroTelefono!;
orderCheckoutDetails['products'] = this.products!.map((product) => product.name).toList();
orderCheckoutDetails['subtotal'] = this.subtotal!;
orderCheckoutDetails['deliveryFee'] = this.deliveryFee!;
orderCheckoutDetails['total'] = this.total!;
return orderCheckoutDetails;
}
And then create / insert your document like:
return _checkoutCollection
.doc(checkout.email!)
.set({
'orderCheckoutDetails' : FieldValue.arrayUnion([checkout.toDocument])
}, SetOptions(merge: true));

Related

Data is null when trying to add auto generated document ID from firebase to object. Why?

I'm streaming a collection from firebase and I want to add the auto generated ID into ProductModel object.
Stream<List<ProductModel>> getprod() {
return _firestore
.collection('macarons')
.snapshots()
.map((QuerySnapshot query) {
List<ProductModel> retVal = [];
query.docs.forEach((element) {
retVal.add(ProductModel.fromJson(element.data(), element.id));
print(element.id);
});
return retVal;
});
}
This is my model
ProductModel.fromJson(
Map<String, dynamic> prod,
String id,
) {
id = id;
description = prod['description'] ?? 'Classic macaron';
flavor = prod['flavor'] ?? 'Choclate';
name = prod["name"] ?? 'empty';
price = prod["price"] ?? 5;
imageurl = prod["imageurl"] ?? 'www.google.com';
}
I do get the ID printed out however it is not rendering on my widget. I get the 'data != null' assertion.
Your problem is in:
id = id;
The left-hand side of the expression is also interpreted as the id parameter, so the assignment becomes a noop.
You'll want to do this instead:
this.id = id;

Flutter - auto complete textfield with suggestions from sqlite database

Auto complete textfield with suggestions from sqlite database. Show one of the data from salute database as title and another as subtitle in the listtile of suggestions. I have used the sqlite plugin
Data model
class Note {
int _id;
String _title;
String _description;
var _chatEnabled;
var _liveEnabled;
var _recordEnabled;
var _raiseEnabled;
var _shareYtEnabled;
var _kickOutEnabled;
String _time;
String _host;
Note(this._title, this._description, this._time, this._chatEnabled, this._liveEnabled, this._recordEnabled, this._raiseEnabled, this._shareYtEnabled, this._kickOutEnabled, this._host);
Note.map(dynamic obj) {
this._id = obj['id'];
this._title = obj['title'];
this._description = obj['description'];
this._time = obj['time'];
this._chatEnabled = obj['chatEnabled'];
this._liveEnabled = obj['liveEnabled'];
this._recordEnabled = obj['recordEnabled'];
this._raiseEnabled = obj['raiseEnabled'];
this._shareYtEnabled = obj['shareYtEnabled'];
this._kickOutEnabled = obj['kickOutEnabled'];
this._host = obj['host'];
}
int get id => _id;
String get title => _title;
String get description => _description;
String get time => _time;
int get chatEnabled => _chatEnabled;
int get liveEnabled => _liveEnabled;
int get recordEnabled => _recordEnabled;
int get raiseEnabled => _raiseEnabled;
int get shareYtEnabled => _shareYtEnabled;
int get kickOutEnabled => _kickOutEnabled;
String get host => _host;
Map<String, dynamic> toMap() {
var map = new Map<String, dynamic>();
if (_id != null) {
map['id'] = _id;
}
map['title'] = _title;
map['description'] = _description;
map['time'] = _time;
map['chatEnabled'] = _chatEnabled;
map['liveEnabled'] = _liveEnabled;
map['recordEnabled'] = _recordEnabled;
map['raiseEnabled'] = _raiseEnabled;
map['shareYtEnabled'] = _shareYtEnabled;
map['kickOutEnabled'] = _kickOutEnabled;
map['host'] = _host;
return map;
}
Note.fromMap(Map<String, dynamic> map) {
this._id = map['id'];
this._title = map['title'];
this._description = map['description'];
this._time = map['time'];
this._chatEnabled = map['chatEnabled'];
this._liveEnabled = map['liveEnabled'];
this._recordEnabled = map['recordEnabled'];
this._raiseEnabled = map['raiseEnabled'];
this._shareYtEnabled = map['shareYtEnabled'];
this._kickOutEnabled = map['kickOutEnabled'];
this._host = map['host'];
}
}
DB manager
class DatabaseHelper {
static final DatabaseHelper _instance = new DatabaseHelper.internal();
factory DatabaseHelper() => _instance;
final String tableRecentJ = 'jrecentTable';
final String columnId = 'id';
final String columnTitle = 'title';
final String columnDescription = 'description';
final String columnTime = 'time';
final String chatEnabled = 'chatEnabled';
final String liveEnabled = 'liveEnabled';
final String recordEnabled = 'recordEnabled';
final String raiseEnabled = 'raiseEnabled';
final String shareYtEnabled = 'shareYtEnabled';
final String kickOutEnabled = 'kickOutEnabled';
final String host = 'host';
static Database _db;
DatabaseHelper.internal();
Future<Database> get db async {
if (_db != null) {
return _db;
}
_db = await initDb();
return _db;
}
initDb() async {
String databasesPath = await getDatabasesPath();
String path = join(databasesPath, 'jrecent.db');
// await deleteDatabase(path); // just for testing
var db = await openDatabase(path, version: 1, onCreate: _onCreate);
return db;
}
void _onCreate(Database db, int newVersion) async {
await db.execute(
'CREATE TABLE $tableRecentJ($columnId INTEGER PRIMARY KEY, $columnTitle TEXT, $columnDescription TEXT, $columnTime TEXT, $chatEnabled INTEGER, $liveEnabled INTEGER, $recordEnabled INTEGER, $raiseEnabled INTEGER, $shareYtEnabled INTEGER, $kickOutEnabled INTEGER, $host TEXT)');
}
Future<int> saveNote(Note note) async {
var dbClient = await db;
var result = await dbClient.insert(tableRecentJ, note.toMap());
// var result = await dbClient.rawInsert(
// 'INSERT INTO $tableNote ($columnTitle, $columnDescription) VALUES (\'${note.title}\', \'${note.description}\')');
return result;
}
Future<List> getAllNotes() async {
var dbClient = await db;
var result = await dbClient.query(tableRecentJ, columns: [columnId, columnTitle, columnDescription, columnTime, chatEnabled, liveEnabled, recordEnabled, raiseEnabled, shareYtEnabled, kickOutEnabled, host]);
// var result = await dbClient.rawQuery('SELECT * FROM $tableNote');
return result.toList();
}
Future<int> getCount() async {
var dbClient = await db;
return Sqflite.firstIntValue(await dbClient.rawQuery('SELECT COUNT(*) FROM $tableRecentJ'));
}
Future<Note> getNote(int id) async {
var dbClient = await db;
List<Map> result = await dbClient.query(tableRecentJ,
columns: [columnId, columnTitle, columnDescription, columnTime, chatEnabled, liveEnabled, recordEnabled, raiseEnabled, shareYtEnabled, kickOutEnabled, host],
where: '$columnId = ?',
whereArgs: [id]);
// var result = await dbClient.rawQuery('SELECT * FROM $tableNote WHERE $columnId = $id');
if (result.length > 0) {
return new Note.fromMap(result.first);
}
return null;
}
Future<int> deleteNote(int id) async {
var dbClient = await db;
return await dbClient.delete(tableRecentJ, where: '$columnId = ?', whereArgs: [id]);
// return await dbClient.rawDelete('DELETE FROM $tableNote WHERE $columnId = $id');
}
Future<int> cleanNote() async {
var dbClient = await db;
return await dbClient.delete(tableRecentJ);
// return await dbClient.rawDelete('DELETE FROM $tableNote WHERE $columnId = $id');
}
Future<int> updateNote(Note note) async {
var dbClient = await db;
return await dbClient.update(tableRecentJ, note.toMap(), where: "$columnId = ?", whereArgs: [note.id]);
// return await dbClient.rawUpdate(
// 'UPDATE $tableNote SET $columnTitle = \'${note.title}\', $columnDescription = \'${note.description}\' WHERE $columnId = ${note.id}');
}
Future close() async {
var dbClient = await db;
return dbClient.close();
}
}
I want to show title and description in the suggestion of the textfield. How get the desired result?
Sorry, but your question is not clear.
I am guessing you want to autocomplete view in your textfile
Here is the package that provides the same.
https://pub.dev/packages/flutter_typeahead
Take look at it.

Flutter:Make model to save json into sqlite and return data as a object

I have this json :
{
"ProductCoreId":0,
"LifeCycle":1,
"OwnerPartyRoleId":0,
"TriggerImages":null,
"TriggerImagesUrl":[
"/Files/Images/Channels/sdp.png"
],
"TriggerChannelIds":[
1287
],
"ActionImages":null,
"ActionImagesUrl":[]
}
As you can see i have 2 list TriggerImagesUrl and TriggerChannelIds. Now i want to save this model of json into sqlite so I made this class model according my json to save into database:
class ToolsByChannelIdDbModel {
int productCoreId;
int lifeCycle;
int ownerPartyRoleId;
Null triggerImages;
String triggerImagesUrl;
String triggerChannelIds;
ToolsByChannelIdDbModel(
{this.productCoreId,
this.lifeCycle,
this.ownerPartyRoleId,
this.triggerImages,
this.triggerImagesUrl,
this.triggerChannelIds});
ToolsByChannelIdDbModel.fromJson(Map<String, dynamic> json) {
productCoreId = json['ProductCoreId'];
lifeCycle = json['LifeCycle'];
ownerPartyRoleId = json['OwnerPartyRoleId'];
triggerImages = json['TriggerImages'];
triggerImagesUrl = json['TriggerImagesUrl'];
triggerChannelIds = json['TriggerChannelIds'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['ProductCoreId'] = this.productCoreId;
data['LifeCycle'] = this.lifeCycle;
data['OwnerPartyRoleId'] = this.ownerPartyRoleId;
data['TriggerImages'] = this.triggerImages;
data['TriggerImagesUrl'] = this.triggerImagesUrl;
data['TriggerChannelIds'] = this.triggerChannelIds;
return data;
}
}
As you now sqlite do not accept list as a type so i made a list as json string so i saved like this:
ToolsByChannelIdDbModel model = ToolsByChannelIdDbModel();
model.productCoreId = item.productCoreId;
model.lifeCycle = item.lifeCycle;
model.ownerPartyRoleId = item.ownerPartyRoleId;
model.triggerImagesUrl = jsonEncode(item.triggerImagesUrl);
model.triggerChannelIds = jsonEncode(item.triggerChannelIds);
db.insert("tools_table", model.toJson());
item.triggerImagesUrl and item.triggerChannelIds are a list and i map these into json string to save into database.Till now everything is good and these list saved into database.
The problem is happening when i make a query and i want to decode the result of db.rawQuery into my object.
Future<List<ToolsByChannelIdDbModel>> getTools(
int channelId) async {
List<ToolsByChannelIdDbModel> list =
try {
var result = await DBProvider.db.getToolsById(channelId);
result.forEach((item) {
list.add(ToolsByChannelIdDbModel.fromJson(item));
});
} catch (e) {
print(e);
}
return list;
}
}
await DBProvider.db.getToolsById(channelId); part works fine and returned a map of data and triggerImagesUrl and triggerChannelIds are json string, but when i want to convert map into my object by ToolsByChannelIdDbModel.fromJson(item) in this section :
String triggerImagesUrl; String triggerChannelIds;
In model i got error because i declare these fields as String but they are lists.
How can i store these lists into database and how can i map them into object?
Try this:
class ToolsByChannelIdDbModel {
int productCoreId;
int lifeCycle;
int ownerPartyRoleId;
dynamic triggerImages;
List<String> triggerImagesUrl;
List<int> triggerChannelIds;
dynamic actionImages;
List<dynamic> actionImagesUrl;
/* Create instance without ID (database will auto generate one if you specified an
id column)*/
ToolsByChannelIdDbModel({
this.lifeCycle,
this.ownerPartyRoleId,
this.triggerImages,
this.triggerImagesUrl,
this.triggerChannelIds,
this.actionImages,
this.actionImagesUrl,
});
// Create instance with ID
ToolsByChannelIdDbModel.withId({
this.productCoreId,
this.lifeCycle,
this.ownerPartyRoleId,
this.triggerImages,
this.triggerImagesUrl,
this.triggerChannelIds,
this.actionImages,
this.actionImagesUrl,
});
/* When you want to add object to database you can use .toMap() this will
convert ToolsByChannelIdDbModel instance to a Map*/
Map<String, dynamic> toMap()
{
var tempMap = Map<String, dynamic>();
if(productCoreId != null)
{
tempMap['productCoreId'] = productCoreId;
}
tempMap['lifeCycle'] = lifeCycle;
tempMap['ownerPartyRoleId'] = ownerPartyRoleId;
tempMap['triggerImages'] = triggerImages;
tempMap['triggerImagesUrl'] = List<dynamic>.from(triggerImagesUrl.map((x) =>
x));
tempMap['triggerChannelIds'] = List<dynamic>.from(triggerChannelIds.map((x) =>
x));
tempMap['actionImages'] = actionImages;
tempMap['actionImagesUrl'] = List<dynamic>.from(actionImagesUrl.map((x) =>
x));
return tempMap;
}
/* Named constructor to create ToolsByChannelIdDbModel instance from a Map that you
get from database */
ToolsByChannelIdDbModel.fromMap(Map<String, dynamic> map)
{
this.productCoreId = map['productCoreId'];
this.lifeCycle = map['lifeCycle'];
this.ownerPartyRoleId = map['ownerPartyRoleId'];
this.triggerImages = map['triggerImages'];
this.triggerImagesUrl = List<String>.from(map['triggerImagesUrl'].map((x) =>
x));
this.triggerChannelIds = List<int>.from(map['triggerChannelIds'].map((x) =>
x));
this.actionImages = map['actionImages'];
this.actionImagesUrl = List<dynamic>.from(map['actionImagesUrl'].map((x) =>
x));
}
}
Hope this helped!!

How do I write tests for a transformed stream in Flutter?

I have a Provider which has a method which takes data from Firebase as a stream, transforms it to a list and returns a Stream<List<Model>> . I'm trying to write a test where I want to check if the items in the List are the same as I expect them to be. How can I do that?
My Current Code:
test('getContacts returns a empty list when there is no contact',() async{
when(sharedPreferencesMock.get(any)).thenReturn('uid'); //mock the sharedprefs
documentSnapshot = DocumentSnapshotMock(); //mock documentsnapshot
when(documentSnapshot.exists).thenReturn(true); // this is done to pass the getUidByUsername method
documentReference = DocumentReferenceMock(documentSnapshotMock: documentSnapshot);
documentReference.setData({
'uid':'uid',
'contacts':[] // setting the usename in the data already so that duplicate contact exception is thrown
});
userDataProvider.getContacts().asBroadcastStream().listen((data){
expect(data.length,0);
});
});
And the provider method
#override
Stream<List<Contact>> getContacts() {
CollectionReference userRef = fireStoreDb.collection(Paths.usersPath);
DocumentReference ref =
userRef.document(SharedObjects.prefs.get(Constants.sessionUid));
return ref.snapshots().transform(StreamTransformer<DocumentSnapshot, List<Contact>>.fromHandlers(handleData: (documentSnapshot, sink) async{
List<String> contacts;
if (documentSnapshot.data['contacts'] == null) {
ref.updateData({'contacts': []});
contacts = List();
} else {
contacts = List.from(documentSnapshot.data['contacts']);
}
List<Contact> contactList = List();
for (String username in contacts) {
print(username);
String uid = await getUidByUsername(username);
DocumentSnapshot contactSnapshot = await userRef.document(uid).get();
contactList.add(Contact.fromFirestore(contactSnapshot));
}
sink.add(contactList);
}));
}
Update:
StreamController streamController = StreamController<List<Contact>>();
StreamSink<List<Contact>> sink = streamController.sink;
Stream<List<Contact>> stream = streamController.stream;
stream.listen((List<Contact> list){
expect(list.length,1);
});
userDataProvider.mapDocumentToContact(userCollection, userRef, documentSnapshot, sink);
streamController.close();
Make the lambda function that you currently pass to the StreamTansformer a separate function and test that.
If you want to test the full function there is a Firebase mock package on pub.

Returning values in async functions - Flutter

Using Cloud Firestore as my DB.
I need to read data from two separate documents in my code, get data from a document based on value in the previous document.
Getting data from firestore in a Future method which looks like this:
Future<List<Map<String, dynamic>>> getGenAlertsData() async{
QuerySnapshot alertRef = await Firestore.instance.collection('alerts').orderBy('time', descending: true).getDocuments();
List<Map<String, dynamic>> messages = new List();
Map<String, dynamic> fullMessage;
// int counter = 0;
alertRef.documents.forEach((doc) async {
// counter++;
String senderKey, senderNameKey, senderSpecKey, senderPicKey, msgBodyKey, timeKey, sender, senderName, senderSpec, msgBody, senderPic;
senderKey = 'sender';
sender = doc['sender'];
timeKey = 'time';
DateTime time = doc['time'];
print(time);
msgBodyKey = 'msgBody';
msgBody = doc['msgBody'];
DocumentReference ref = Firestore.instance.document('/staff/'+sender);
print(ref.documentID);
await ref.get().then((onValue) { //values not populated here :(
senderNameKey = 'senderName';
senderName = onValue['name'];
print(senderName);
senderPicKey = 'senderPic';
senderPic = onValue['profilePic'];
senderSpecKey = 'specialization';
senderSpec = onValue['specialization'];
});
// await print(senderName);
fullMessage = <String, dynamic> {
senderKey: sender,
senderNameKey: senderName,
senderSpecKey: senderSpec,
senderPicKey: senderPic,
timeKey: time,
msgBodyKey: msgBody,
};
messages.add(fullMessage);
print(messages.toString()); // I get the messages printed in my console here
}); //loop over collection ends here
print(messages.toString()); // No data here :(
return messages;
}
At this point only the values from alertRef.documents.forEach((doc) are avaialble, and the values inside await ref.get().then((onValue) { are not populated (they are null in the fullMessage Map) .
Any help will be greatly appreciated.
Yaay! Figured it out finally!
Apparently, then() doesn't work that well inside async functions, now my code looks like this, and everything's great now! :)
Put it all in a good old for loop and everything''s fine now.
Future<List<Map<String, dynamic>>> getGenAlertsData() async{
QuerySnapshot alertRef = await Firestore.instance.collection('alerts').orderBy('time', descending: true).getDocuments();
List<Map<String, dynamic>> messages = new List();
Map<String, dynamic> fullMessage;
// String xxx;
String senderKey, senderNameKey, senderSpecKey, senderPicKey, msgBodyKey, timeKey, sender, senderName, senderSpec, msgBody, senderPic;
List<DocumentSnapshot> alertSnaps= alertRef.documents;
for (int i = 0; i < alertSnaps.length; i++)
{
senderKey = 'sender';
sender = alertSnaps[i]['sender'];
timeKey = 'time';
DateTime time = alertSnaps[i]['time'];
print(time);
msgBodyKey = 'msgBody';
msgBody = alertSnaps[i]['msgBody'];
DocumentSnapshot snappy = await Firestore.instance.document('/staff/'+sender).get();
senderNameKey = 'senderName';
senderName = snappy['name'];
print('Doc for sender' + senderName);
senderPicKey = 'senderPic';
senderPic = snappy['profilePic'];
senderSpecKey = 'specialization';
senderSpec = snappy['specialization'];
fullMessage = <String, dynamic> {
senderKey: sender,
senderNameKey: senderName,
senderSpecKey: senderSpec,
senderPicKey: senderPic,
timeKey: time,
msgBodyKey: msgBody,};
messages.add(fullMessage);
}
return messages;
}

Resources