Flutter/Dart/Firebase - Updated nested map values - firebase

I am trying to record which on my users has purchased which ticket in my app. I am using firebase to store my data about my users and giveaways. When a purchase is complete, I am trying to update the relevant giveaway and assign each ticket to a user using their id.
Firstly, I am not sure if my data schema is the most appropriate for what I'm trying to achieve so open to suggestions for editing as I'm still quite new to the flutter world.
Second, here is how my data is currently structured:
Here is how I have structured my code. Here is my SingleBasketItem model:
class SingleBasketItem {
final String id;
final String uid;
final OurGiveAways giveaway;
final int ticketNumber;
SingleBasketItem(this.id, this.uid, this.giveaway, this.ticketNumber);
}
Here is my Shopping Basket model, I have added an Elevated Button to my shopping basket page which, when tapped, will execute the placeOrder() function:
class ShoppingBasket extends ChangeNotifier {
Map<String, SingleBasketItem> _items = {};
Map<String, SingleBasketItem> get items {
return {..._items};
}
void addItem(String id, String uid, OurGiveAways giveaway, int ticketNumber) {
_items.putIfAbsent(
id,
() => SingleBasketItem(id, uid, giveaway, ticketNumber),
);
notifyListeners();
}
void placeOrder(BuildContext context, OurUser user) {
for (var i = 0; i < _items.length; i++) {
FirebaseFirestore.instance
.collection('giveaways')
.doc(_items.values.toList()[i].giveaway.giveawayId)
.update(
{
'individual_ticket_sales'[_items.values.toList()[i].ticketNumber]:
user.uid,
},
);
}
}
}
Below is an image of the results:
By analysing the results it looks like my code is creating a new field with a title of the 1st index character of individual_ticket_sales ("n" because ive bought ticket 1), how can I set the nested "1" (or whatever ticket I choose) to my user id rather than creating a new field? Thanks.

I would recommend to refactor your database structure because the first problem you will hit with that one is that firestore for now does not support updating a specific index for an array field value. You can get more info about that here.
You could get the whole value individual_ticket_sales update it and save it again as whole but it would be just a matter of time when you would hit the problem that multiple users want to update the same value on almost the same time and one of the changes get's lost. Even the usage of transaction would not be 100% safe because of the size of the object and potential multiple changes.
Is it possible for you to store each ticketId as a firestore document in a firestore collection like this:
FirebaseFirestore.instance
.collection('giveaways')
.doc(_items.values.toList()[i].giveaway.giveawayId)
.collection('individual_ticket_sales')
.doc(i)
.update(user.uid);

Related

I want to CRUD the value of the FireStore in the method

・ What I want to do.
I have stored the document ID of the currently accessed room in user collection>document>field>documentID.
I want to retrieve it and rewrite the document in the room collection.
But I can't rewrite it.
I want to know how to get the data and rewrite it.
[The document ID in the user collection is the user ID.]
I want to retrieve the field in the following method
void _onConferenceTerminated(message) async {
//[Image 1] I get the documentID being accessed from the documentID in the field of the user collection
final user = FirebaseFirestore.instance.collection('user').doc(uid()).get();
final getDocId = user.data['documentID']
//[*image2]Use it to access the room document and reduce the roomCount
final setRoomCount = await FirebaseFirestore.instance.
.collection('room')
.doc(getDocId)
.set({'roomCount': roomCount - 1});
}
//Get the user ID
String uid() {
final User user = FirebaseAuth.instance.currentUser!
final String uid = user.uid.toString();
return uid
[image1]
[image2]
You could use .update() instead of .set() if you want to change only one field. I also recommend you to use FieldValue.increment(value) which is built-in function in firestore.
Value could be both int and double.
FieldValue.increment(1)
FieldValue.increment(2.0)
Also, you can use negative numbers to decrease the value. In your case, you can try the below code.
await FirebaseFirestore.instance
.collection("room")
.doc(getDocId)
.update({"roomCount": FieldValue.increment(-1)}
Documentation: https://firebase.google.com/docs/firestore/manage-data/add-data#increment_a_numeric_value

Flutter Firebase firestore append data with unique ID

I'm working on the Flutter app where users can save multiple addresses. Previously I used a real-time database and it was easier for me to push data in any child with a unique Id but for some reason, I changed to Firestore and the same thing want to achieve with firestore. So, I generated UUID to create unique ID to append to user_address
This is how I want
and user_address looks like this
And this is how it's getting saved in firestore
So my question Is how I append data with unique id do I have to create a collection inside users field or the above is possible?
Below is my code I tried to set and update even user FieldValue.arrayUnion(userServiceAddress) but not getting the desired result
var uuid = Uuid();
var fireStoreUserRef =
await FirebaseFirestore.instance.collection('users').doc(id);
Map locationMap = {
'latitude': myPosition.latitude,
'longitude': myPosition.longitude,
};
var userServiceAddress = <String, dynamic>{
uuid.v4(): {
'complete_address': completedAddressController.text,
'floor_option': floorController.text,
'how_to_reach': howtoreachController.text,
'location_type': locationTag,
'saved_date': DateTime.now().toString(),
'user_geo_location': locationMap,
'placeId': addressId
}
};
await fireStoreUserRef.update({'user_address': userServiceAddress});
If I use set and update then whole data is replaced with new value it's not appending, so creating a collection is the only solution here and If I create a collection then is there any issue I'll face?
You won't have any issues per se by storing addresses in a separate collection with a one-to-many relationship, but depending on your usage, you may see much higher read/write requests with this approach. This can make exceeding your budget far more likely.
Fortunately, Firestore allows updating fields in nested objects via dot notation. Try this:
var userServiceAddress = {
'complete_address': completedAddressController.text,
'floor_option': floorController.text,
'how_to_reach': howtoreachController.text,
'location_type': locationTag,
'saved_date': DateTime.now().toString(),
'user_geo_location': locationMap,
'placeId': addressId
};
await fireStoreUserRef.update({'user_address.${uuid.v4()}': userServiceAddress});

Can I have duplicate document IDs?

My application uses dates as document Id.
Each day stores the items the user bought and their total price.
The user might buy items from his local store twice or more in a day.
How can I allow that in my flutter code?
All the questions I found, ask about how not to duplicate document ID. I guess I'm the only one who want to do this.
The code just in case:
/// Save to collection [user], document [day], the list of items in [cartMap], and the total amount [total]
static Future<void> saveToFirestore(String day, Map<GroceryItem, int> cartMap, double total) async {
bool paidStatus = false;
List<String> x = day.split('-');
assert(matches(x[2], '[0-9]{2}')); // make sure the day is two digits
assert(matches(x[1], '\\b\\w{1,9}\\b')); // make sure the month is a word between 1 and 9 length
assert(matches(x[0], '[0-9]{4}')); // make sure it's a number year of four digits
final groceries = Groceries.cartMap(cartMap).groceries;
final user = await CurrentUser.getCurrentUser();
CollectionReference ref = Firestore.instance.collection(user.email);
DocumentReference doc = ref.document(day);
final dateSnapshot = await doc.get();
Firestore.instance.runTransaction((transaction) async {
updatePaidUnpaidTotal(day, user.email, total);
if(dateSnapshot.exists) {
print('Updating old document');
List<dynamic> existingItems = dateSnapshot.data['items'];
var totalItems = existingItems + groceries;
return await transaction.update(doc, {'total': FieldValue.increment(total), 'items': totalItems, 'paidStatus': false},);
}
else {
print('New document');
return await transaction.set(doc, {'total': total, 'items': groceries, 'paidStatus': paidStatus});
}
});
}
I strongly recommend not using dates (or any actual data at all) in a document ID, for the reason that you're running into.
There is rarely a requirement in an app to use a specific format for a document ID. It might be convenient in some cases, but ultimately, it constrains the future expansion of your collection.
One flexible way of adding any document at all, is to simply use add() to accept a random ID for the new document. Then you can put the date in a field in the document, and use that in your queries as a filter on that field. This is going to give you much more freedom to change things up later if you want, so you are not bound the dates for IDs. There are no real performance penalties for this.
The only reason to use a non-random string as a document ID is if you need to absolutely enforce uniqueness on that value. Since your application no longer wants to enforce this, it's better to simply remove that constraint and use random IDs.
It is not possible to have several Firestore documents in the same (sub)collection with the same document ID.
A classical approach in your case is to create one document per day and create a subcollection which contains a document for each item the user bought during this day.
Something like:
orders (collection)
- 20200725 (doc)
- orderItems (subcollection)
- KJY651KNB9 (doc with auto generate id): {'items': totalItems, 'paidStatus': false ... }
- 20200726 (doc)
- orderItems (subcollection)
- AZEY543367 (doc with auto generate id): {'items': totalItems, 'paidStatus': false ... }
- AZEY5JKKLJ (doc with auto generate id): {'items': totalItems, 'paidStatus': false ... }
- AZEY598T36 (doc with auto generate id): {'items': totalItems, 'paidStatus': false ... }

Create user profile page using Bloc, RxDart and Flutter

My goal is to create an edit profile page in Flutter using the bloc pattern.
I've searched for a better/cleaner way to create a user profile page in Flutter using Bloc, but I can't find anything.
Right now I have to list every field out. Here's an example of 2 fields:
final _firstNameController = BehaviorSubject<String>();
final _lastNameController = BehaviorSubject<String>();
Function(String) get firstNameChanged => _firstNameController.sink.add;
Function(String) get lastNameChanged => _lastNameController.sink.add;
Stream<String> get firstNameStream => _firstNameController.stream;
Stream<String> get lastNameStream => _lastNameController.stream;
String get firstName => _firstNameController.value;
String get lastName => _lastNameController.value;
#override
void dispose() {
_firstNameController?.close();
_lastNameController?.close();
}
There are a lot more fields and I don't want to have all this code if I can avoid it.
I'd prefer to only have 1 user bloc and update the specific field of that user. I've added the following to a user bloc.
final _userFetcher = BehaviorSubject<User>();
Observable<User> get userStream => _userFetcher.stream;
User get user => _userFetcher.value;
Function(User) get changeUser => _userFetcher.sink.add;
#override
void dispose() async {
await _userFetcher.drain();
_userFetcher.close();
}
Here's an example of my user model:
class User {
final Name name;
User.fromJson(Map<String, dynamic> json)
: name = Name.fromJson(json);
Map<String, dynamic> toJson() => {
"first": name.first,
"last": name.last,
};
}
The issue is I can't figure out how to use a textfield to edit the "fist name" and "last name" fields in my "User" model.
Is this possible, am I going about this the wrong way, or should I stick to listing every field out individually?
To individually manage all those streams for each individual fields can be cumbersome. I would recommend you to try out this library flutter bloc . It is a really good one that handles the state management pretty well. You just need to define the states and events and then for each event you can generate a state.
So for example you want to validate password field as the user is typing. You define an Event(eg. PasswordChanged). This will call a method in the bloc. In which you can write your business logic to check the validation.
After your validation logic you can yield a new State(error, succcess). This will cause your UI to be rebuilt and then you can update your UI according to your state.
You should checkout the documentation of this library. It also has some very good examples there.
Thank you everyone for your help!
I ended up being able to edit the nested first and last name fields in my user model I listed above by implementing the following in my textField:
onChanged:(text) {
User thisUser = _bloc.user;
thisUser.name.last = text;
}

flutter query multiple collections in firestore

I am playing with flutter but I ran into an issue with firestore that I can't figure out.
let's say I would like to retrieve the purchaser history of a customer and I have a firestore as described below so I have a collection of "user" within that contains a document of user_id's and then within that, I have a "products" collection with a document that contains the product_id's my aim is to gather the product id's for the selected user and then retrieve the products price from the products collection?
users
user_id_1
products
purchace_id
product_id
quantity
products
product_id
product_desc
price
the issue I am having is while I can get the id for the products from the user table but I can't then see a simple way of how I can use this data like with SQL where we would make a simple join on the product_id to get the price where the id's match?
any help would be much appreciated.
Thanks for replying Frank van Puffelen 2 seprate querys seams reasonable but it brings up other issues with the second query as now with the code below i can get the documents_id from the user and then do a query on the products and everything looks ok so i think i am going in the correct direction as i can print the id of the document in the loop to ensure i am accessing the correct ones but when i change this to get a snapshot of the document i cant access the data within this is probably me doing something silly or misunderstanding something but this feels very awkward compared to sql when trying to get and work with the data. For example if i return the data it wants it to be in the form of a future> however once i return the data in that format then i can no longer access the id's in the same way as i was doing.
Future<List<DocumentSnapshot>> getSeedID() async{
var data = await Firestore.instance.collection('users').document(widget.userId).collection('products').getDocuments();
var productList = data.documents;
print("Data length: ${productList.length}");
for(int i = 0; i < productList.length; i++){
var productId = Firestore.instance.collection('products').document(productList[i]['productId']).documentID;
if(productId != null) {
print("Data: " + productId);
}
}
return productList;
}
Short answer i didn't pay attention to how you use a future
Get the productId from the user table
Future<List<DocumentSnapshot>> getProduceID() async{
var data = await Firestore.instance.collection('users').document(widget.userId).collection('Products').getDocuments();
var productId = data.documents;
return productId;
}
Then call this using .then() rather than try to apply the returned data to the variable as that is not how a future it works
var products;
getProduceID().then((data){
for(int i = 0; i < s.length; i++) {
products = Firestore.instance.collection('products')
.document(data[i]['productID'])
.snapshots();
if (products != null) {
products.forEach((product) {
print(product.data.values);
});
}
}
});

Resources