I'm writing a Flutter application which uses the user's location to identify items around them. As it's only possible to do comparison queries against one field in Firebase, I'm using geohashes as per Frank van Puffelen's lecture on the subject.
I've got the initial query working fine:
qs = await Firestore.instance
.collection('users')
.where('geohash', isGreaterThanOrEqualTo: boundaryGeoHash1)
.where('geohash', isLessThanOrEqualTo: boundaryGeoHash2)
.getDocuments();
this returns all the people in the required geographic area, as expected. The problem is, I expect there to be a high volume of users, so I want to limit the amount of downloads per query to a certain number - say 10.
When I write this query (which I believed should do the trick), what actually seems to happen is that the table is ordered by 'geohash', it then queries the first 10 items in the table, and then sends me the records in the first 10 items that fulfil the requirements of the .where part of the query:
qs = await Firestore.instance
.collection('users')
.orderBy('geohash')
.where('geohash', isGreaterThanOrEqualTo: boundaryGeoHash1)
.where('geohash', isLessThanOrEqualTo: boundaryGeoHash2)
.startAfterDocument(dS)
.limit(10)
.getDocuments();
//dS = last document from previous query
whereas what I want the query to do is to always send me 10 results, all of which fulfil the .where part of the query.
Using the test database I've generated, calling the initial query returns 114 results. The second one finds 2 which brings me to my conclusion as to what is happening.
Can what I'm trying to do be achieved? And if so, any clues as to what I'm doing wrong?
EDIT
As per Frank's request, dS is populated as follows:
if (qs != null) {
dS = qs.documents[qs.documents.length - 1];
}
//dS is a global variable
EDIT 2
Here is the function which does the query on the database.
Future<List<CutDownUserProfile>> getReducedUserDataBetweenGeohashes(
String boundaryGeoHash1, String boundaryGeoHash2, DocumentSnapshot dS, int downloadLimit) async {
List<CutDownUserProfile> retn = List<CutDownUserProfile>();
QuerySnapshot qs;
if (dS != null) {
print('ds != null');
print('dS.userNumber = ' + dS.data['userNumber'].toString());
print('dS.userID = ' + dS.data['userID']);
print('dS.geohash = ' + dS.data['geohash']);
print('dS.name = ' + dS.data['name']);
qs = await Firestore.instance
.collection('userHeaders')
.orderBy('geohash')
.where('geohash', isGreaterThanOrEqualTo: boundaryGeoHash1)
.where('geohash', isLessThanOrEqualTo: boundaryGeoHash2)
.startAfterDocument(dS)
.limit(downloadLimit)
.getDocuments();
} else {
print('ds == null');
qs = await Firestore.instance
.collection('userHeaders')
.orderBy('geohash')
.where('geohash', isGreaterThanOrEqualTo: boundaryGeoHash1)
.where('geohash', isLessThanOrEqualTo: boundaryGeoHash2)
.limit(downloadLimit)
.getDocuments();
}
if (qs.documents.isNotEmpty) {
print('through this! qs len = ' + qs.documents.length.toString());
List<DocumentSnapshot> ds = qs.documents.toList();
print('/////DS Len = ' + ds.length.toString());
for (int i = 0; i < ds.length; i++) {
CutDownUserProfile cDUP = CutDownUserProfile();
cDUP.geoHash = ds[i].data['geohash'];
Vector2 latLon = globals.decodeGeohash(cDUP.geoHash);
double dist = getDistanceFromLatLonInKm(globals.localUser.homeLat, globals.localUser.homeLon, latLon.x, latLon.y);
if (dist <= globals.localUser.maxDistance && dist <= ds[i].data['maxDist']) {
CutDownUserProfile cDUP2 = CutDownUserProfile();
cDUP2.userNumber = ds[i].data['userNumber'];
cDUP2.userID = ds[i].data['userID'];
cDUP2.geoHash = ds[i].data['geohash'];
retn.add(cDUP2);
}
}
if (qs != null) {
globals.lastProfileSeen = qs.documents[qs.documents.length - 1];
}
} else {
print('no results');
}
}
The line print('/////DS Len = ' + ds.length.toString()); prints out the length of the queries returned from the search, and, as mentioned earlier returns 2. Without the .orderBy the .startAfterDocument(dS) and the .limit(downloadLimit) the code returns 114 results, which is what I expected. For completeness, here is the original code:
Future<List<CutDownUserProfile>> getReducedUserDataBetweenGeohashesALL(String boundaryGeoHash1, String boundaryGeoHash2) async {
List<CutDownUserProfile> retn = List<CutDownUserProfile>();
await Firestore.instance
.collection('userHeaders')
.where('geohash', isGreaterThanOrEqualTo: boundaryGeoHash1)
.where('geohash', isLessThanOrEqualTo: boundaryGeoHash2)
.getDocuments()
.then((event) {
if (event.documents.isNotEmpty) {
List<DocumentSnapshot> ds = event.documents.toList(); //if it is a single document
print('/////DS Len = ' + ds.length.toString());
for (int i = 0; i < ds.length; i++) {
CutDownUserProfile cDUP = CutDownUserProfile();
cDUP.geoHash = ds[i].data['geohash'];
Vector2 latLon = globals.decodeGeohash(cDUP.geoHash);
double dist = getDistanceFromLatLonInKm(globals.localUser.homeLat, globals.localUser.homeLon, latLon.x, latLon.y);
CutDownUserProfile cDUP2 = CutDownUserProfile();
cDUP2.userNumber = ds[i].data['userNumber'];
cDUP2.userID = ds[i].data['userID'];
cDUP2.geoHash = ds[i].data['geohash'];
retn.add(cDUP2);
}
} else {
print('no results');
}
}).catchError((e) => print("error fetching data: $e"));
Related
I recently tried to create a command with discord_components Buttons to create a channel logs and a welcome channel, the bot asks if there is a channel logs or welcome, and if the user answers "No" then the bot creates a channel logs or welcome. Normally I should get the 'Finish' at the end of the command message but instead I get the Interaction Failed error.
#bot.command(name="configserver")
#has_permissions(administrator=True)
#bot_has_permissions(administrator=True)
async def configserver(ctx):
def check(res):
return ctx.author == res.user and res.channel == ctx.channel
try:
file = open("file", "x")
except FileExistsError:
os.remove("file")
file = open("file", "a")
guild: discord.Guild = ctx.guild
embed_button = await ctx.send(embed=discord.Embed(
title="Configuration",
description="There is a **logs** channel ?",
color=get_color(0x3ef76f, 0xe8f73e, 0xf73e3e)),
components=[[
Button(style=ButtonStyle.green, label="Yes"),
Button(style=ButtonStyle.red, label="No")
]])
# For channel logs
res = await bot.wait_for("button_click", check=check)
if res.component.label == "Yes":
await res.respond(
content=await embed_button.edit(embed=discord.Embed(
title="Configuration",
description="There is a **welcome** channel ?",
color=get_color(0x3ef76f, 0xe8f73e, 0xf73e3e)),
components=[[
Button(style=ButtonStyle.green, label="Yes"),
Button(style=ButtonStyle.red, label="No")
]])
)
res = await bot.wait_for("button_click", check=check)
await res.respond(
type=InteractionType.ChannelMessageWithSource,
content='Finish'
)
elif res.component.label == "No":
permissions_logs = {
guild.default_role: discord.PermissionOverwrite(read_messages=False),
}
channel_logs: discord.TextChannel = await guild.create_text_channel('logs', overwrites=permissions_logs)
print(channel_logs.id)
file.write(f"channel_logs = {channel_logs.id}\n")
await res.respond(
content=await embed_button.edit(embed=discord.Embed(
title="Configuration",
description="There is a **welcome** channel ?",
color=get_color(0x3ef76f, 0xe8f73e, 0xf73e3e)),
components=[[
Button(style=ButtonStyle.green, label="Yes"),
Button(style=ButtonStyle.red, label="No")
]])
)
# For channel logs
res = await bot.wait_for("button_click", check=check)
if res.component.label == "Yes":
print('Yes')
await res.respond(
type=InteractionType.ChannelMessageWithSource,
content='Finish'
)
elif res.component.label == "No":
print('No')
permissions_welcome = {
guild.default_role: discord.PermissionOverwrite(write_messages=False),
}
channel_welcome = await guild.create_text_channel('welcome', overwrites=permissions_welcome)
print(channel_welcome.id)
file.write(f"channel_welcome = {channel_welcome.id}\n")
await res.respond(
type=InteractionType.ChannelMessageWithSource,
content='Finish'
)
We have a table with a large amount of data and I do not want to load it at once for my dxDataGrid.
I want to implement paging with Skip / Take which is supplied from the dxDataGrid's DataSourceLoadOptions.
This is my controller:
[HttpGet]
public async Task<Object> GetSalesOrdersWithTotals(DataSourceLoadOptions loadOptions)
{
try
{
var results = await SalesOrderService.GetSalesOrdersWithTotals(loadOptions.Skip, loadOptions.Take, 40);
loadOptions.Skip = 0;
loadOptions.Take = 0;
return DataSourceLoader.Load(results, loadOptions);
}
catch (Exception ex)
{
return Json(new { code = "422", success = false, message = "Unable to fetch sales orders with totals - " + ex.ToString() });
}
}
This is the service that returns the data:
public async Task<IEnumerable<SalesOrderWithTotals>> GetSalesOrdersWithTotals(int skip, int take, int defaultPageSize)
{
if (take == 0)
{
//Fix for passing a 0 take
take = defaultPageSize;
}
var salesOrderWithTotals =
from o in _context.SalesOrder
select new SalesOrderWithTotals
{
SalesOrderId = o.SalesOrderId,
Net = _context.SalesOrderItem.Where(it => it.SalesOrderId == o.SalesOrderId).Select(it => it.Qty == null ? 0 : it.Qty.Value * it.UnitPrice == null ? 0 : it.UnitPrice.Value).Sum(),
Tax = _context.SalesOrderItem.Where(it => it.SalesOrderId == o.SalesOrderId).Select(it => it.Qty == null ? 0 : it.Qty.Value * it.UnitPrice == null ? 0 : it.UnitPrice.Value).Sum() * (o.Tax.Percentage /100),
Gross = _context.SalesOrderItem.Where(it => it.SalesOrderId == o.SalesOrderId).Select(it => it.Qty == null ? 0 : it.Qty.Value * it.UnitPrice == null ? 0 : it.UnitPrice.Value).Sum() + _context.SalesOrderItem.Where(it => it.SalesOrderId == o.SalesOrderId).Select(it => it.Qty == null ? 0 : it.Qty.Value * it.UnitPrice == null ? 0 : it.UnitPrice.Value).Sum() * (o.Tax.Percentage / 100),
Name = o.Customer.Name,
CustomerOrderNumber = o.CustomerOrderNumber,
Contact = o.Contact,
OrderDate = o.OrderDate
};
return await salesOrderWithTotals.Skip(skip).Take(take).ToListAsync();
}
Looking at SQL profiler, this takes the first 40 records but of course the dxDataGrid is not aware of the total count of records so pagination is not available.
What would be the best method to achieve what I want in this case?
Many thanks
You must do an extra query to get the count of your SalesOrder and keep it in for example salesOrderCount. Then keep the Load method return data as bellow.
LoadResult result = DataSourceLoader.Load(results, loadOptions);
LoadResult has a parameter called totalCount so set it with the real count of your data:
result.totalCount = salesOrderCount;
and then
return result;
Now the dxDataGrid is aware of the total count of records.
I got an error when I try to get Time set data from Cloud Firestore.
I think if I set timestampsInSnapshots: true then the issue will be fixed, but I can't set it because I use cloud_firestore: ^0.16.0 so I couldn't found how can I do this. if I use cloud_firestore: ^0.8.2+1 then I can configure the Firestore's settings. But I wanna set this configuration in version 0.16.0
About Issue:
The following _TypeError was thrown building StreamBuilder<QuerySnapshot>(dirty, state: _StreamBuilderBaseState<QuerySnapshot, AsyncSnapshot<QuerySnapshot>>#ec8a0):
type 'Timestamp' is not a subtype of type 'int'
The relevant error-causing widget was
StreamBuilder<QuerySnapshot>
lib/…/main/profile.dart:66
When the exception was thrown, this was the stack
#0 _ProfileState.buildExamHistoryList.<anonymous closure>.<anonymous closure>
lib/…/main/profile.dart:97
#1 MappedListIterable.elementAt (dart:_internal/iterable.dart:411:31)
#2 ListIterator.moveNext (dart:_internal/iterable.dart:340:26)
#3 new _GrowableList._ofEfficientLengthIterable (dart:core-patch/growable_array.dart:188:27)
#4 new _GrowableList.of (dart:core-patch/growable_array.dart:150:28)
...
enter image description here
The stream where I wanna set my data from firestore:
Widget buildExamHistoryList() {
return StreamBuilder<QuerySnapshot>(
stream: usersRef.doc(widget.userID).collection('examResults').snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Center(
child: Text("Something Went Wrong"),
);
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Container(
padding: EdgeInsets.only(top: 100),
child: Center(
child: SpinKitFadingCircle(
color: Colors.black,
size: 50,
),
),
);
break;
default:
return Column(
children: [
ListView(
shrinkWrap: true,
children: snapshot.data.docs.map((doc) {
return Padding(
padding: const EdgeInsets.all(10),
child: ExamHistoryCard(
correctAnswersCount: doc['correctAnswersCount'],
incorrectAnswersCount: doc['incorrectAnswersCount'],
date: _examHistoryService.readTimestamp(doc['date']),
),
);
}).toList(),
),
],
);
}
},
);
}
and that is my readTimestamp function:
String readTimestamp(int timestamp) {
var now = DateTime.now();
var format = DateFormat('HH:mm a');
var date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
var diff = now.difference(date);
var time = '';
if (diff.inSeconds <= 0 ||
diff.inSeconds > 0 && diff.inMinutes == 0 ||
diff.inMinutes > 0 && diff.inHours == 0 ||
diff.inHours > 0 && diff.inDays == 0) {
time = format.format(date);
} else if (diff.inDays > 0 && diff.inDays < 7) {
if (diff.inDays == 1) {
time = diff.inDays.toString() + ' Dünen';
} else {
time = diff.inDays.toString() + ' Gün Önce';
}
} else {
if (diff.inDays == 7) {
time = (diff.inDays / 7).floor().toString() + ' Hefte Önce';
} else {
time = (diff.inDays / 7).floor().toString() + ' Hefte Önce';
}
}
return time;
}
The dates you get from firebase in doc['date'] is a Timestamp, not an int. You can transform it into a Date by using toDate() method or to milliseconds since epoch with toMillis() like this:
final Timestamp timestamp = doc['date'];
final DateTime date = timestamp.toDate();
final int millis = timestamp.toMillis()
Also be sure to declare the correct type you are going to be passing to your readTimestamp function:
String readTimestamp(Timestamp timestamp) {}
or
String readTimestamp(DateTime date) {}
or
String readTimestamp(int millis) {}
Try this
String readTimestamp(Timestamp timestamp) {
var now = DateTime.now();
var format = DateFormat('HH:mm a');
var date = timestamp.toDate();
var diff = now.difference(date);
var time = '';
if (diff.inSeconds <= 0 ||
diff.inSeconds > 0 && diff.inMinutes == 0 ||
diff.inMinutes > 0 && diff.inHours == 0 ||
diff.inHours > 0 && diff.inDays == 0) {
time = format.format(date);
} else if (diff.inDays > 0 && diff.inDays < 7) {
if (diff.inDays == 1) {
time = diff.inDays.toString() + ' Dünen';
} else {
time = diff.inDays.toString() + ' Gün Önce';
}
} else {
if (diff.inDays == 7) {
time = (diff.inDays / 7).floor().toString() + ' Hefte Önce';
} else {
time = (diff.inDays / 7).floor().toString() + ' Hefte Önce';
}
}
return time;
}
Timestamp from Firebase will be formatted as Timestamp or Map.
Here DatetimeConverter taking care of the conversion from/to json from Firebase Firestore and Cloud Functions:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
class DatetimeConverter implements JsonConverter<DateTime, dynamic> {
const DatetimeConverter();
#override
dynamic toJson(DateTime object) {
return object != null
? Timestamp.fromDate(object)
: FieldValue.serverTimestamp();
}
#override
DateTime fromJson(dynamic val) {
Timestamp timestamp;
if (val is Timestamp) {
// the firestore SDK formats the timestamp as a timestamp
timestamp = val;
} else if (val is Map) {
// cloud functions formate the timestamp as a map
timestamp = Timestamp(val['_seconds'] as int, val['_nanoseconds'] as int);
}
if (timestamp != null) {
return timestamp.toDate();
} else {
// Timestamp probably not yet written server side
return Timestamp.now().toDate();
}
}
}
I have this piece of code like this:
var options = new DynamoDBOperationConfig
{
ConditionalOperator = ConditionalOperatorValues.Or,
OverrideTableName = nameTable,
ConsistentRead = true
};
new QueryOperationConfig()
{
IndexName = indexName,
Filter = queryFilter,
Select = SelectValues.Count
};
result = context.FromQueryAsync<TEntity>(queryConfig, options).GetRemainingAsync().Result;
as per the documentation, it should return just the count of values that match the filter, at least, the piece of code in the SelectValues class says that
//
// Summary:
// An enumeration of all supported Select values for Query and Scan. Value of Count
// will force service to return the number of items, not the items themselves.
but result is always an empty list; how can i make the count work ?
If you are still looking for the answer, this is the solution:
new QueryOperationConfig()
{
IndexName = indexName,
Filter = queryFilter,
Select = SelectValues.Count,
ConsistentRead = true
};
var table = context.GetTargetTable<TEntity>();
var search = table.Query(queryConfig);
result = search.Count;
Having ConsistentRead set to true will cause it to give you real time updates when the table is updated.
It's not working on Document level...
You can try to do this in low level model.
int count = 0;
Dictionary<string, AttributeValue> lastKey = null;
do
{
var request = new QueryRequest
{
TableName = "tableNmae",
IndexName = "indexName",
KeyConditionExpression = "ID= :v_ID",
ExpressionAttributeValues = new Dictionary<string, AttributeValue>
{
{
":v_ID",
new AttributeValue
{
N = "1"
}
}
},
ConsistentRead = false,
Select = Select.COUNT,
ExclusiveStartKey = lastKey
};
var respone = await tableClient.QueryAsync(request);
count += respone.Count;
lastKey = respone.LastEvaluatedKey;
} while (lastKey != null && lastKey.Count != 0);
As i run a snapshot from a firebase database below it returns
Optional(498895446)
when i only want it to return
498895446
as an int. I have tried toint() but it is not working as i get an error. How can i get rid of this optional.
let ref = FIRDatabase.database().reference().child("Users + infomation").child(currentuser).child("timeStamp ")
ref.observeSingleEventOfType(.Value, withBlock : {(snapShot) in
let val = snapShot.value
if snapShot.exists(){
print("\(val)")
}
else if snapShot.exists() == false {
print("snappyaintexist")
}
})
Try:-
let ref = FIRDatabase.database().reference().child("Users + infomation").child(currentuser).child("timeStamp ")
ref.observeSingleEventOfType(.Value, withBlock : {(snapShot) in
if let val = snapShot.value as? Int{
print("\(val!)")
}else{
print("snappyaintexist")
}
})