How to retrieve a Map values inside a field of a specific document in firestore using flutter? - android

I want to get all the Values of the map which is under "Subjects" field. And I also want to listen if any updates or changes made to those values. I actually want to add al those values and store in a variable. Can someone please tell me how can i achieve this?
Here is the structure of the collection.
This is how im adding the subjects whenever a user enters the subject name
onPressed: () async {
temp = qrdataFeed.text;
int index = qrdataFeed.text.indexOf('-');
subject = qrdataFeed.text.substring(0, index);
print("Subject name is $subject");
numberOfClasses = await FirebaseFirestore.instance
.collection('tutors')
.doc(uid)
.get()
.then((doc) async {
Map<String, dynamic> map = await doc.data();
if (!map.containsKey('Subjects')) {
await tutor_details.doc(uid).set({
'Subjects': {'$subject': initialValue},
}, SetOptions(merge: true));
}
if (doc.data()['Subjects']['$subject'] !=
'$subject') {
if (!map.containsKey('$subject')) {
await tutor_details.doc(uid).set({
'Subjects': {'$subject': initialValue}
}, SetOptions(merge: true));
}
}
var val = await doc.data()['Subjects']['$subject'];
return val;
});
if (!mounted) return;
setState(() {
qrData = qrdataFeed.text;
scanned = true;
print('done');
//if (temp != qrdataFeed.text)
numberOfClasses += 1;
print('$numberOfClasses is printed');
});
await tutor_details.doc(uid).set({
'Subjects': {'$subject': numberOfClasses},
}, SetOptions(merge: true));
}
},

Posting the #NisanthReddy's solution as a Community Wiki for visibility.
A better architecture would be to have one class at the root to handle all your transactions and to also notify any listener in your code.
You should always call this class to update and you can add listeners to this class from any widget you want. This way you will have everything Firestore-related in one place.

Related

Always update widget flutter

I want a widget or something where my int automatically updates with my realtime database. Does anyone knows how i can do this. The code that i want to update the whole time is:
userid = _userId.replaceAll('#', "").replaceAll("[", "").replaceAll("]", "");
DatabaseReference ref = FirebaseDatabase.instance.ref("credits");
final snapshot = await ref.child('$userid').get();
if (snapshot.exists) {
moneyd = snapshot.value.toString();
print(snapshot.value);
} else {
print('No data available.');
}
print('hello $moneyd');
if (int.tryParse(moneyd) != null) {
money = int.tryParse(moneyd) ?? 0;
} else {
print('Invalid value for moneyd: $moneyd');
}
The money value is now hard coded: money = 10; but i want it to be int money = int.parse(monyd);. I want this because, the string is 10 everytime that i restart the app but i want it to be the last saved string in firebase.
String moneyd = '';
In this case, he will need to use a StreamBuilder. FirebaseDatabase lets you listen for events:
StreamBuilder<Event>(
stream: ref.child('credits')child('$userid').onValue,
builder: (context, snapshot) {
if (snapshot.hasData) {
// Get a list of messages from snapshot data
List<dynamic> data = snapshot.data.snapshot.value;
moneyd = snapshot.value.toString();
print(snapshot.value);
// Return a ListView widget to display messages
return Text(moneyd);
} else if (snapshot.hasError) {
return Text('Something went wrong');
} else {
return Text('Loading...');
}
},
),

Inside a Map function, a Firebase query id checker function runs list length number times and halt other operation

In my orders list map Function I used another function that checks every order unique id from firebase database that if the id is exists on any document, if it's available then It should put the order item under that reference document if not then in It should create a new reference document and make a new list for that kind of order, but my when It runs inside the loop checking id query runs first and then runs the order placing part, so if fails the checking system
Here is the map function
orderNowList
.map(
(item) => {
orderRequestMaker(
item['title'],
item['product_ref_id'],
item['image_main'],
item['quantity'],
item['price_per_unit'],
item['total_price'],
item['delivery_charge'],
item['buyer_id'],
item['shop_id'],
item['shop_name'],
item['shop_image'],
uniqueId,
),
// removeRequestMaker(item['product_ref_id']),
},
)
.toList(),
Here is the order function
orderRequestMaker(
productName,
productId,
productImage,
productQuantity,
productUnitPrice,
productTotalPrice,
productDeliveryCharge,
buyerId,
shopId,
shopName,
shopImage,
uniqueId) async {
var orderRefId;
QuerySnapshot<Object> currentShopChecker = await FirebaseFirestore.instance
.collection('orders')
.where('ids', isEqualTo: uniqueId)
.get();//first it only runs this query loop limit times, then goes down, but I want
// to run query, get the result and do the rest of the operation
bool statusCheck = currentShopChecker.docs.length == 0;
statusCheck ? orderRefId = '' : orderRefId = currentShopChecker.docs[0].id;
DateTime presentTime = DateTime.now();
if (statusCheck) {
var orderRef = FirebaseFirestore.instance.collection('orders').doc();
orderRefId = orderRef.id;
orderRef.set({
'created_at': presentTime,
'buyer_name': buyerName,
'buyer_id': buyerId,
'ids': buyerId + shopId,
'buyer_image': buyerImage,
'shop_name': shopName,
'shop_id': shopId,
'shop_image': shopImage,
});
}
if (!statusCheck) {
await FirebaseFirestore.instance
.collection('orders')
.doc(orderRefId)
.update({
'created_at': presentTime,
});
}
await FirebaseFirestore.instance
.collection('orders')
.doc(orderRefId)
.collection('order_group')
.doc()
.set({
'created_at': presentTime,
'product_title': productName,
'product_id': productId,
'product_image': productImage,
'product_quantity': productQuantity,
'product_unit_price': productUnitPrice,
'product_total_price': productTotalPrice,
'product_delivery_charge':
productDeliveryCharge != null ? productDeliveryCharge : '',
'buyer_id': buyerId,
'shop_id': shopId,
'status': 'Order placed'
});
}

Flutter FCM: Storing Notification Data while the app is in the Background/Closed

I am trying to store data received via FCM inside a class in order to navigate to a specific tab of my app after a user clicks on that notification.
My problem is that, as far as I could find on the web, the MainActivity is stopped when the app is not in the Foreground, and so, when I try and retrieve this data, I am not getting the updated variables. I have checked this using print statements throughout the app.
In order to store the information and use it when the app is brought back up, do I need to create a local database, or is there another way around this problem?
PS: I have a Stream that receives information that the user has clicked on the notification, and it updates the main page, but I cannot retrieve anything else from it, as it itself doesn't receive the json.
Thank you. Also, this was my first question posted here, be gentle if I didn't follow the protocol by the letter.
Sample code below.
Initialization:
FirebaseOption options = FirebaseOptions(
apiKey: 'XXXXXXXXXXXXXXXXXXXXXXXXXXx',
appId: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
messagingSenderId: 'XXXXXXXXXXXXXX',
projectId: 'XXXXXXXXXXXXx',
);
Future<void> backgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp(
options: options,
);
var decoded = await NotificationModelPusherAG.fromJson(message.data);
var encodedMessage = await json.decode(decoded.message);
var decodedMessage = await PusherMessage.fromJson(encodedMessage);
notifications.type = message.data;
FirebaseNotifications.showNotification(decodedMessage.title, decodedMessage.description, message.data);
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final String INSTANCE_ID = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
await PusherBeams.start(INSTANCE_ID);
await Firebase.initializeApp(
options: options,
);
/// Run this funk when a notification is received and app is in BG
FirebaseMessaging.onBackgroundMessage(backgroundHandler);
runApp(MyApp());
}
The class in which the data is stored:
class Notifications {
var tab = 0;
int get returnTab => return tab;
final _notificationUpdateController = BehaviorSubject();
Stream get update => _notificationUpdateController.stream;
shouldUpdate(add) {
_notificationUpdateController.sink.add(add);
}
void set type(messageData) {
if (messageData['type'] == 'xxxxxxx') {
this.tab = 1;
}
}
var notifications = Notifications();
The widget that should update:
StreamBuilder<Object>(
stream: notifications.update,
builder: (context, snapshot) {
if (updateMulti == true) {
print(notifications.returnTab); /// Here it is '0'
return multiScreen;
} else {
return multiScreen;
}
}
);
And the function that updates it:
flutterNotificationPlugin.initialize(
initSettings,
onSelectNotification: onSelectNotification
);
static Future onSelectNotification(String payload) {
print(payload); /// The payload is always null for some reason
print(notifications.returnTab); /// Here it shows '1' as it should
updateMulti = true;
notifications.shouldUpdate(true);
}
I kind of shortened the code a bit, if I missed something important do tell me, and I shall update accordingly.
Thank you again.

Read data from cloud firestore with firebase cloud function?

I'm an Android developer and recently I've started working on a project based on firebase cloud functions and firestore database. I'm writing an HTTP trigger function that will take two parameters and compare that parameter value with the firestore data value and if the value matches then return a response of true or else false.
Duplicate Question:
Yes, there are some question already asked related to mine but they are not similar:
Firestore + cloud functions: How to read from another document
Firebase HTTP Cloud Functions - Read database once
Firebase Docs says:
Cloud Firestore supports create, update, delete, and write events
I want to read firestore value from HTTP trigger.
What I have tried:
exports.userData = functions.https.onRequest((req, res) => {
const user = req.query.user;
const pass = req.query.pass;
});
I'm pretty much stuck at this part. Any help will be greatly appreciated. Thanks
P.S. I have very limited knowledge related to JS/TypeScript/NodeJS
a bit late, but for any one else stumbling upon this.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.someMethod = functions.https.onRequest((req, res) => {
var stuff = [];
var db = admin.firestore();
db.collection("Users").doc("7vFjDJ63DmhcQiEHwl0M7hfL3Kt1").collection("blabla").get().then(snapshot => {
snapshot.forEach(doc => {
var newelement = {
"id": doc.id,
"xxxx": doc.data().xxx,
"yyy": doc.data().yyy
}
stuff = stuff.concat(newelement);
});
res.send(stuff)
return "";
}).catch(reason => {
res.send(reason)
})
});
Thanks to Ruan's answer, here's an example for onCall(..) variation:
exports.fireGetColors = functions.https.onCall((data, context) => {
return new Promise((resolve, reject) => {
var colors = {};
var db = admin.firestore();
db.collection('colors')
.get()
.then(snapshot => {
snapshot.forEach(doc => {
var key = doc.id;
var color = doc.data();
color['key'] = key;
colors[key] = color;
});
var colorsStr = JSON.stringify(colors, null, '\t');
console.log('colors callback result : ' + colorsStr);
resolve(colors);
})
.catch(reason => {
console.log('db.collection("colors").get gets err, reason: ' + reason);
reject(reason);
});
});
});
In 2022, I am trying to do this thing in "Modular" way as what firebase has for version >= 9. Using typescript too as an addition :). Thanks to Ruan for the inspiration.
So, here is how I made it ( similar to the following ):
import * as functions from "firebase-functions";
import { getFirestore } from "firebase-admin/firestore";
import { initializeApp } from "firebase-admin/app";
initializeApp(functions.config().firebase);
export const someMethod = functions.https.onRequest((req, res) => {
let stuff: any[] = [];
let db = getFirestore();
db.collection("Users").doc("7vFjDJ63DmhcQiEHwl0M7hfL3Kt1").collection("blabla").get().then(snapshot => {
snapshot.forEach(doc => {
var newelement = {
"id": doc.id,
"xxxx": doc.data().xxx,
"yyy": doc.data().yyy
}
stuff = stuff.concat(newelement);
});
res.send(stuff)
return "";
}).catch(reason => {
res.send(reason)
})
});

Firebase Cloud function triggers twice onUpdate

I have below rules set for my firebase collection - payments
"payments": {
"$paymentId": {
"totalAmount": {
},
"balanceAmount": {
".validate": "newData.val()<=data.child('totalAmount').val()"
},
"paymentDetails": {
"$detailId": {
"amount": {
".validate": "newData.isString()"
},
}
},
}
}
and below cloud function written to handle certain updates on that collection:
exports.calculateBalance = functions.database
.ref('payments/{pushId}')
.onUpdate(event => {
const paymentRef = event.data.adminRef;
const payment = event.data.val();
return paymentRef.once('value').then(snapshot => {
var paidAmount = 0;
snapshot.child("paymentDetails").forEach(function(child) {
paidAmount += parseFloat(child.child("amount").val());
});
return paidAmount;
}).then(snap => {
payment.balanceAmount = parseFloat(payment.totalAmount) - snap;
return paymentRef.set(payment);
})
});
Its simple, whenever I add a payment details, I want to update the balanceAmount. The problem here is, whenever an update occurs at that collection, the function triggers twice. First time its obvious from application and 2nd time its because of paymentRef.set(payment); line.
Is there any possible way I can avoid this 2nd trigger on cloud function? I cannot use any flag on collection level since the update on payment details happens multiple times. Can someone guide me in the right direction on this?
EDIT
Note - I've an edit option for payment details entered.
If you want update the balance amount only when you add a payment details you can use the onCreate trigger directly on the payment details.
Try something like that (You have to update the code for your case) :
exports.calculateBalance = functions.database.ref('payments/{pushId}/paymentDetails/{detailId}').onCreate(event => {
var paymentRef = event.data.adminRef.parent.parent;
var paymentDetailSnapshot = event.data;
var paymentDetailAmountSnapshot = paymentDetailSnapshot.child('amount');
return paymentRef.child('balanceAmount').transaction(current => {
return current - paymentDetailAmountSnapshot.val(); // Balance can be negative
});
});
(Use the transaction to manage concurrent modifications)
Transactions documentation.
NOTE
Please, use directly a Number for your amount data not a String.
UPDATE
Add this function to manage the update :
exports.recalculateBalance = functions.database.ref('payments/{pushId}/paymentDetails/{detailId}').onUpdate(event => {
var paymentRef = event.data.adminRef.parent.parent;
var paymentDetailSnapshot = event.data;
var previousPaymentDetailSnapshot = event.data.previous;
var paymentDetailAmountSnapshot = paymentDetailSnapshot.child('amount');
var previousPaymentDetailAmountSnapshot = previousPaymentDetailSnapshot.child('amount');
return paymentRef.child('balanceAmount').transaction(current => {
return current + previousPaymentDetailAmountSnapshot.val() - paymentDetailAmountSnapshot.val(); // Balance can be negative
});
});

Categories

Resources