Flutter Firestore query with startAfter - firebase

I am using Flutter (cloud_firestore) and trying to get data from Firestore after document with title 'xxx', but it returns 0 results.
return Firestore.instance.collection('products')
.orderBy('title')
.startAfter([{'title': 'xxx'}
]);
What am I doing wrong? How can I properly implement flutter pagination?

You should pass a value, not a map:
return Firestore.instance.collection('products')
.orderBy('title')
.startAfter(['xxx']);
The documentation on this is not particularly clear.

This is what worked for me. My orderBy is based on {FirstName, LastName & Email}. So, the startAfter should also match these field values. I saved the document in a variable and later made a list with the values of those fields and used as startAfter parameter.
static final Query queryBase = userCollectionRef
.orderBy(FieldNames.FIRST_NAME)
.orderBy(FieldNames.LAST_NAME)
.orderBy(FieldNames.EMAIL);
DocumentSnapshot _lastUser;
Future<List<DocumentSnapshot>> getAllUserDocuments({
bool next,
int limit = 10,
}) async {
List<DocumentSnapshot> _userDocSnaps = [];
try {
if (!next) _lastUser = null;
Query _query = queryBase;
if (_lastUser != null)
_query = _query.startAfter([
_lastUser.data[FieldNames.FIRST_NAME],
_lastUser.data[FieldNames.LAST_NAME],
_lastUser.data[FieldNames.EMAIL],
]);
var _userDocsSnap = await _query.limit(limit).getDocuments();
if (_userDocsSnap != null && _userDocsSnap.documents != null) {
_userDocSnaps = _userDocsSnap.documents;
if (_userDocSnaps != null && _userDocSnaps.length >= 1)
_lastUser = _userDocSnaps[_userDocSnaps.length - 1];
}
} catch (err) {
String errMessage = 'Exception in method _getAllUserDocuments';
PrintHelper.handlePrint(errMessage, err);
}
return _userDocSnaps;
}

You can pass entire doc using startAfterDocument method
_db
.collection('requirement')
.orderBy('createdAt', descending: true)
.startAfterDocument(lastVisible)

Related

Pagination with StartAfter not working on a firestore collection group

I have the below query which keeps returning the same 5 documents on every call, instead of fetching the next group of documents as I would expect.
DocumentSnapshot<Object?>? lastFetchedDoc;
Future<void> getReviews() async {
Query<Map<String, dynamic>> query = firestore
.collectionGroup("reviews")
.where("city", whereIn: ['New York', 'Philadelphia', 'Washington'])
.orderBy('time_of_posting', descending: true) // timestamp
.limit(5);
if (lastFetchedDoc != null) {
query.startAfterDocument(lastFetchedDoc!);
}
QuerySnapshot snapshot = await query.get();
lastFetchedDoc = snapshot.docs.last;
}
Any ideas what the issue could be here.
Thanks
Calling startAfterDocument returns a new query, so you need to hold on to that return value:
if (lastFetchedDoc != null) {
query = query.startAfterDocument(lastFetchedDoc!);
}

I want to convert realtime database to cloud firestore

Below is the code that I need to convert
DatabaseReference driversRef =
FirebaseDatabase.instance.reference().child("drivers");
driversRef
.child(currentfirebaseUser!.uid)
.once()
.then((DataSnapshot dataSnapShot) {
if (dataSnapShot.value != null) {
driversInformation = Drivers.fromSnapshot(dataSnapShot);
}
})
I have set up cloud store but I can't seem to get the code write to translate what I have to firestore.
This is my try
:
driversRef
.doc(currentfirebaseUser!.uid)
.collection('earnings')
.get()
.then((value) {
if (value != null) {
driversInformation = value.toString();
}
});
below is a picture of database structure
I think you are using collection('earning) after the Doc is wrong. because you can use it in starting & once just like table name.
you can try this
final databaseReference = FirebaseFirestore.instance;
databaseReference.collection('drivers').doc(currentfirebaseUser!.uid).snapshots().listen((event) {
//If you want to listen like streaming
evenyt.data(); //<- access data in <String,dynamic>
}
// If you want to list only once
var response = await databaseReference.collection('drivers').doc(currentfirebaseUser!.uid).get();
response.data()?['earning'] ?? 0 // access specific key value
here I show you my example similar to you:
I am updating notification badge values when it will change.
here is my cloud firestore structure.
//If you want to listen only once
Future<int> getBadgeCount({required String uid}) async {
var response = await databaseReference.collection('Badges').doc(uid).get();
return (response.data() != null) ? (response.data()?['BadgeCount'] ?? 0) : 0;
}
//If you want to listen like streaming
databaseReference.collection('Badges').where(uid).limit(1).snapshots().listen((response) {
badgeCount = response.docs.isNotEmpty ? (response.docs.first.data()['BadgeCount'] ?? 0) : 0;
printLog(message: "BadgeCount: ", variable: badgeCount);
});
}

Read list of items from firebase database flutter

I'm trying to build a list of items from this database:
but i'm getting this error:
_TypeError (type 'List<dynamic>' is not a subtype of type 'List<DataSnapshot>')
i can get the item values just using snap2.value but i need to get the element key because it's my item id, to build my list.
import 'package:firebase_database/firebase_database.dart';
final DatabaseReference _refdata =
FirebaseDatabase.instance.reference().child('host');
getItems() async {
String _refOnline;
await _refdata.child("ref").once().then((value) => _refOnline = value.value);
if (dataRef != _refOnline) {
await _refdata.child("values/valores").once().then((DataSnapshot snap2) {
List<DataSnapshot> result = snap2.value;
lista = result
.map((element) => Item(
element.key,
element.value["nome"],
element.value["preco"].toDouble(),
element.value["precoantes"] ?? "",
element.value["tipo"],
element.value["disponivel"],
element.value["descricao"],
element.value["calorias"]))
.toList();
});
edit:
with this change im able to return values:
List<Map<dynamic, dynamic>> result =
List<Map<dynamic, dynamic>>.from(snap2.value);
result.forEach((element) {
if (element != null) {
print(element);
}
});
but i cant return the keys (1,2,3,4,5)
and it's the same as doing this(suposing query ordered by keys):
List<dynamic> result = snap2.value;
int _i = 1;
result.forEach((value) {
if (value != null) {
lista.add(Item(
_i.toString(),
value["nome"],
value["preco"].toDouble(),
value["precoantes"] ?? "",
value["tipo"],
value["disponivel"],
value["descricao"],
value["calorias"]));
_i += 1;
print(value["nome"]);
print(value);
print(lista.length);
}
and now im getting this error:
NoSuchMethodError (NoSuchMethodError: The method 'add' was called on null.
Receiver: null
Tried calling: add(Instance of 'Item'))
There is no way in FlutterFire to get the child nodes as a list of DataSnapshot objects.
The closest I got was:
currentRoundListener = dbRoot.child('rounds/$currentRoundKey').once.then((snapshot) {
currentRound = List<String>.from(snapshot.value as List<dynamic>);
});
You could give this a try with:
List<DataSnapshot> result = List<DataSnashot>.from(snap2.value as List<dynamic>);
But more likely the values under the snapshot will only be available as a map:
Map<String, dynamic> result = Map<String, dynamic>.from(snap2.value as Map<dynamic, dynamic>);
If you need to maintain the order of the child nodes, have a look here: Flutter: Firebase Real-Time database orderByChild has no impact on query result and here: Flutter Firebase Database wrong timestamp order
I've managed to solve it using this implementation:
getItems() async {
String _refOnline;
List<dynamic> result;
await _refdata.child("ref").once().then((value) => _refOnline = value.value);
if (dataRef != _refOnline) {
await _refdata.child("values/valores").once().then((DataSnapshot snap2) {
result = snap2.value;
int _i = 1;
result.forEach((value) {
if (value != null) {
Item _a = Item(
_i.toString(),
value["nome"],
value["preco"].toDouble(),
value["precoantes"] ?? "",
value["tipo"],
value["disponivel"],
value["descricao"],
value["calorias"]);
lista.add(_a);
_i += 1;
}
});
savepref("ref_key", _refOnline);
});
} else {
lista = [oi, oi2, oi3, oi4, oi5, oi6, oi7, oi8, oi9, oi10, oi11, oi12];
//readIt();
}
}
there was a problem on lista definition blocking it to receive new items in lista.add function.
I've now defined lista as:
List<Item> lista = [];
And with the above function, everything now works.
thx Frank van Puffelen !

How to check if firestore database document exist when given a document id?

I want to check if a document in firestore exist when given a document id. So far I have tried this:
String getUserType(String uid) {
final result = Firestore.instance.collection('patients').document(uid).get();
if (result == null) {
return 'null';
} else {
return 'exist';
}
you can use result.exists.
original post: https://stackoverflow.com/a/56465899/4465386
final result = await Firestore.instance
.collection('posts')
.document(docId)
.get()
if (result == null || !result.exists) {
// Document with id == docId doesn't exist.
}

DynamoDb : Scan query does not return all the data

I have a DynamoDb table with thousands of data. I am scanning the table using Scan function and I have applied "Between" FilterExpression.
However , the query response only gives 3 records whereas it should return about 100 records.
I have created the Lambda function using Node js.
The other common issue could be whether the scan is executed until LastEvaluatedKey is empty.
If you are already doing this and still not getting all the items, please show your code to look at it in detail.
If the total number of scanned items exceeds the maximum data set size
limit of 1 MB, the scan stops and results are returned to the user as
a LastEvaluatedKey value to continue the scan in a subsequent
operation. The results also include the number of items exceeding the
limit. A scan can result in no table data meeting the filter criteria.
If LastEvaluatedKey is empty, then the "last page" of results has been
processed and there is no more data to be retrieved.
If LastEvaluatedKey is not empty, it does not necessarily mean that
there is more data in the result set. The only way to know when you
have reached the end of the result set is when LastEvaluatedKey is
empty.
Here's example code to get all results:
Map<String, AttributeValue> lastKeyEvaluated = null;
do {
ScanRequest sr = new ScanRequest()
.withTableName("tableName")
.withProjectionExpression("id")
.withExclusiveStartKey(lastKeyEvaluated);
ScanResult result = client.scan(sr);
for (Map<String, AttributeValue> item : result.getItems()) {
System.out.println(item.get("id").getS());
}
lastKeyEvaluated = result.getLastEvaluatedKey();
} while (lastKeyEvaluated != null);
Using Node.js I'm actually using the Query to retrieve the items from the database. A single Query operation can retrieve a maximum of 1 MB of data. That's why I have created a recursive function to retrieving and concatenation data from the database until we receiving LastEvaluatedKey from the response.
When we receiving LastEvaluatedKey as null, that means there are no more data.
My function uses the index to get data from the database. Using the Query functions will work more faster and effectively than Scan.
Actually, getItemByGSI function has a lot of parameters for filtering and customization of the query, which can be useful. And for sure you can remove the parameters which are not nesses for your cases.
So getAllItemsByGSI function can be used to retrieve all data from the DynamoDB, and getItemByGSI can be used to use a single Query.
'use strict';
const omitBy = require('lodash/omitBy');
const isNil = require('lodash/isNil');
const AWS = require('aws-sdk');
const call = (action, params) => {
return new Promise((resolve, reject) => {
try {
const dynamoDb = new AWS.DynamoDB.DocumentClient();
resolve(dynamoDb[action](params).promise());
} catch (error) {
reject(error);
}
});
};
const getItemByGSI = ({
TableName,
IndexName,
attribute,
value,
sortKey,
sortValue,
filter,
filterValue,
operator,
filter1,
filterValue1,
LastEvaluatedKey,
ScanIndexForward,
Limit,
}) => {
return new Promise(async (resolve, reject) => {
try {
const params = {
TableName,
IndexName,
KeyConditionExpression: '#attrKey = :attrValue',
ExpressionAttributeValues: { ':attrValue': value },
ExpressionAttributeNames: { '#attrKey': attribute },
ExclusiveStartKey: LastEvaluatedKey,
Limit,
FilterExpression: null,
};
sortKey && sortValue
? (params.KeyConditionExpression +=
' and #sortKey = :sortValue' &&
(params.ExpressionAttributeNames['#sortKey'] = sortKey) &&
(params.ExpressionAttributeValues[':sortKey'] = sortValue))
: '';
filter && filterValue
? (params.FilterExpression = `#${filter} = :${filter}`) &&
(params.ExpressionAttributeNames[`#${filter}`] = filter) &&
(params.ExpressionAttributeValues[`:${filter}`] = filterValue)
: '';
filter && filterValue && operator && filter1 && filterValue1
? (params.FilterExpression += ` ${operator} #${filter1} = :${filter1}`) &&
(params.ExpressionAttributeNames[`#${filter1}`] = filter1) &&
(params.ExpressionAttributeValues[`:${filter1}`] = filterValue1)
: '';
params = omitBy(params, isNil);
if (ScanIndexForward === false)
params.ScanIndexForward = ScanIndexForward;
const result = await call('query', params);
resolve(result);
} catch (error) {
reject(error);
}
});
};
const getAllItemsByGSI = (data) => {
return new Promise(async (resolve, reject) => {
try {
const finalData = [];
const gettingData = await getItemByGSI(data);
finalData = finalData.concat(gettingData.Items);
if (gettingData.LastEvaluatedKey) {
const final2 = await getAllItemsByGSI({
...data,
LastEvaluatedKey: gettingData.LastEvaluatedKey,
});
finalData = finalData.concat(final2);
}
resolve(finalData);
} catch (err) {
reject(err);
}
});
};
module.exports = {
getItemByGSI,
getAllItemsByGSI,
};

Resources