I am making a flutter application and I’m using cloud firestore as my online database. One of the features in my app is looking for nearby users and showing their profile to the current user in a custom widget on screen. The way I do it is I get current user’s location (either live location or the address saved in database) and then go through every single user in my database collection for users. I get the address of the user from the stored data, calculate the distance using Distance Matrix API and then if distance is less than a specific number (e.g 10000 meter) I create the profile widget for the user to show it on screen.
There are 2 problems:
1- if my number of users grows (for example a million users), By going through every user detail and calculating the distance, It can take a really long time to get the results on the screen . Right now, I only have 20 users for testing purposes and when I search for nearby users, it can take 30 seconds for the results to show up on the screen.
2- With a slow internet connection, the waiting time can be much much more and it can use lots of user’s data for this simple task.
How can I improve this feature and make it faster?
(The current idea that I have is dividing user’s in different documents with respect to their location and then going through only one of the documents by using current user’s location. The problem is that how to divide the addresses efficiently and find the best addresses to look for.)
Below is the code where I find nearby users and add them to a list which I pass to my custom widget class.
final List<UserBoxDesign> listOfBoxes = [];
final FirebaseUser currentUser = await auth.currentUser();
final String currentUserId = currentUser.uid;
if (_userLocationSwitchValue == false) { //use default address of the user
currentUserAddress = await _databaseManagement.getUserCollectionValues(
currentUserId, "address");
} else {
//currentUserAddress = //to do, get device location here.
}
if (_searchValue == SearchValues.Users) {
final List<String> userIds = await _databaseManagement.getUserIds();
for (String id in userIds) {
final String otherUserLocations =
await _databaseManagement.getUserCollectionValues(id, "address");
final String distanceMeters = await _findDistanceGoogleMaps(
currentUserAddress, otherUserLocations);
if (distanceMeters == "Address can't be calculated" ||
distanceMeters == "Distance is more than required by user") {
//if it's not possible to calculate the address then just don't do anything with that.
} else {
final double distanceValueInKilometers = (double.parse(
distanceMeters) /
1000)
.roundToDouble();
final String userProfileImageUrl =
await _databaseManagement.getUserCollectionValues(id, "image");
final String username =
await _databaseManagement.getUserCollectionValues(id, "username");
listOfBoxes.add(
UserBoxDesign( //it creates a custom widget for user if user is nearby
userImageUrl: userProfileImageUrl,
distanceFromUser: distanceValueInKilometers,
userId: id,
username: username,
),
); //here we store the latest values inside the reserved data so when we create the page again, the value will be the reservedData value which is not empty anymore
}
print(listOfBoxes);
}
listOfBoxes.sort((itemA,itemB)=>itemA.distanceFromUser.compareTo(itemB.distanceFromUser)); //SORTs the items from closer to more far from user (we can reverse it to far comes first and close goes last)
setState(() {
_isSearchingForUser = false;
});
return listOfBoxes;
Here is the code where I calculate the distance between origin address and destination address.
Future<String> _findDistanceGoogleMaps(
String originAddress, String destinationAddress) async {
final String url =
"https://maps.googleapis.com/maps/api/distancematrix/json?units=metric&origins=$originAddress&destinations=$destinationAddress&key=$GoogleMapsAPIKey";
try {
final response = await http.get(url);
final responseDecoded = json.decode(response.body);
final distanceInMeters = double.parse(responseDecoded["rows"][0]
["elements"][0]["distance"]["value"]
.toString()); //this is the value in meters always so for km , divide by 1000.
if (distanceInMeters < 100000) {
return distanceInMeters.toString();
} else {
return "Distance is more than required by user";
}
} catch (e) {
return "Address can't be calculated";
}
}
this is how my screen looks when I find nearby users.
if you give code example it will be easy to answer.
I can suggest (we had similar task) to use coordinates longitude and latitude and make requests in your range.
So you don't need Distance Matrix API (I think it will be expensive) and your queries will be fast and cheap.
I googled and found out answer here
How to run a geo "nearby" query with firestore?
==after updated question==
You try to use all the logic inside screen and do it synchronously. Because of that you have such a long rendering time.
You calculate everything on user device and pass to a widget return listOfBoxes;
Instead you can try to use Streambuilder, example is here How to efficiently access a firestore reference field's data in flutter?
Organise data in your database in a such way, that you can make request for your purposes: "Find all users within X range sorted by distance AND ...".
In this case Firebase will do it very quickly and pass data to your Streambuilder asynchronously.
I guess that you can keep longitude, latitude and work with them instead of addresses.
Sorry, I cant rewrite your code, not enough info. Just look example by link, there is a good example.
==update 2==
The package https://pub.dev/packages/geoflutterfire
allow to store geo data to Firestore and how to make requests
// Create a geoFirePoint
GeoFirePoint center = geo.point(latitude: 12.960632, longitude: 77.641603);
// get the collection reference or query
var collectionReference = _firestore.collection('locations');
double radius = 50;
String field = 'position';
Stream<List<DocumentSnapshot>> stream = geo.collection(collectionRef: collectionReference)
.within(center: center, radius: radius, field: field);
Related
I am new to Salesforce Marketing Cloud and journey builder.
https://developer.salesforce.com/docs/marketing/marketing-cloud/guide/creating-activities.html
We are building journey builder's custom activity in which it will use a data extension as the source and when the journey builder is invoked, it will fetch a row and send this data to our company's internal endpoint. The team got that part working. We are using the postmonger.js.
I have a couple of questions:
Is there a way to retrieve the data from data extension in bulk so that we can call our company's internal bulk endpoint? Calling the endpoint for each record in the data extension for our use case would not be efficient enough and won't work.
When the journey is invoked and an entry in the data extension is retrieved and that data is sent to our internal endpoint, is there a machanism to mark this entry as already sent such that next time the journey is run, it won't process the entry that's already sent?
Here is a snippet of our customActivity.js in which this is populating one record. (I changed some variable names.). Is there a way to populate multiple records such that when "execute" is called, it is passing a list of payloads as input to our internal endpoint.
function save() {
try {
var TemplateNameValue = $('#TemplateName').val();
var TemplateIDValue = $('#TemplateID').val();
let auth = "{{Contact.Attribute.Authorization.Value}}"
payload['arguments'].execute.inArguments = [{
"vendorTemplateId": TemplateIDValue,
"field1": "{{Contact.Attribute.DD.field1}}",
"eventType": TemplateNameValue,
"field2": "{{Contact.Attribute.DD.field2}}",
"field3": "{{Contact.Attribute.DD.field3}}",
"field4": "{{Contact.Attribute.DD.field4}}",
"field5": "{{Contact.Attribute.DD.field5}}",
"field6": "{{Contact.Attribute.DD.field6}}",
"field7": "{{Contact.Attribute.DD.field7}}",
"messageMetadata" : {}
}];
payload['arguments'].execute.headers = `{"Authorization":"${auth}"}`;
payload['configurationArguments'].stop.headers = `{"Authorization":"default"}`;
payload['configurationArguments'].validate.headers = `{"Authorization":"default"}`;
payload['configurationArguments'].publish.headers = `{"Authorization":"default"}`;
payload['configurationArguments'].save.headers = `{"Authorization":"default"}`;
payload['metaData'].isConfigured = true;
console.log(payload);
connection.trigger('updateActivity', payload);
} catch(err) {
document.getElementById("error").style.display = "block";
document.getElementById("error").innerHtml = err;
}
console.log("Template Name: " + JSON.stringify(TemplateNameValue));
console.log("Template ID: " + JSON.stringify(TemplateIDValue));
}
});
Any advise or idea is highly appreciated!
Thank you.
Grace
Firstly, i implore you to not proceed with the design pattern of fetching data for each subscriber, from Marketing Cloud, that gets sent through the custom activity, for arguments sake i'll list two big issues.
You have no way of limiting the configuration of data extensions columns or column names in SFMC (Salesforce Marketing Cloud). If any malicious user or by human error would delete a column or change a column name your service would stop receiving that value.
Secondly, Marketing Cloud has 2 sets of API limitations, yearly and minute by minute. Depending on your licensing, you could run into the yearly limit.
The problem you have with limitation on minutes (2500 for REST and 2000 for SOAP) is that each usage of the custom activity in journey builder would multiple the amount of invocations per minute. Hitting this limit would cause issues for incremental data flows into SFMC.
I'd also suggest not retrieving any data from Marketing Cloud when a customer gets sent through a custom activity. Users should pick which corresponding rows/data that should be sent to the custom activity in their segmentation.
The eventDefinitionKey can be picked up from postmonger after requestedTriggerEventDefinition in the eventDefinitionModel function. eventDefinitionKey can then be used to programmatically populate SFMC's POST call with data from the Journey Data model, thus allowing marketers to select what data to be sent with the subscriber.
Following is some code to show how it would work in your customActivity.js
connection.on(
'requestedTriggerEventDefinition',
function (eventDefinitionModel) {
var eventKey = eventDefinitionModel['eventDefinitionKey'];
save(eventKey);
}
);
function save(eventKey) {
// subscriberKey fetched directly from Contact model
// columnName is populated from the Journey Data model
var params = {
subscriberKey: '{{Contact.key}}',
columnName: '{{Event.' + eventKey + '.columnName}}',
};
payload['arguments'].execute.inArguments = [params];
}
In my Alexa Skill i set dynamic entities in 'addRequestInterceptors' as the entities must be available as soon as possible, the issue is that if the user says 'Alexa, open SKILLNAME' the entities are initialized correctly, but if the user instead says 'Alexa, ask SKILLNAME where is DYNAMICENTITY' at this point the skill fails as the DYNAMICENTITY is not yet set.
So my question is, how can i set my dynamic entities as soon as possible like when the skill is created? I've tryed the LaunchIntent but as in the case above it's avoided if the user make the question inside the invocation..
So LaunchIntent and addRequestInterceptors aren't good to do it..
Here is my code which i should run as soon as possible:
const LoadListaPuntiVendita = {
async process(handlerInput) {
const {attributesManager, requestEnvelope} = handlerInput;
const sessionAttributes = attributesManager.getSessionAttributes();
const attributes = await attributesManager.getPersistentAttributes() || {};
const piva = attributes.hasOwnProperty('piva') ? attributes.piva : null;
sessionAttributes["piva"] = piva;
if (!piva || piva === null) {
return;
}
let negozi = sessionAttributes['negozi']
if (!negozi) {
const response = await logic.fetchNegozi(piva);
sessionAttributes['negozi'] = response;
addDynamicEntities(handlerInput.responseBuilder, 'PuntoVenditaType', response);
}
}
};
Here's one possible bug on first glance. This will only run as expected after you've had at least one exchange in which "piva" had been saved and set as a persistent value for the customer.
If you're running this the moment the skill is initialized, it still must be run after the persistence adapter has been initialized and retrieved the value of "piva" from your store AND there must be have been a value stored for "piva" or the code returns early without setting the entity.
Hello i have tweet id's and i saved them to database before. But i saw that i could not save created time efficiently (it is saved like 00:00:00). Therefore i wished to update my tweets with tweet id by using following code.
MyConnectionBuilder myConnection = new MyConnectionBuilder();
Twitter twitter = new TwitterFactory(myConnection.configuration.build()).getInstance();
Status status = twitter.showStatus(Long.parseLong(tweetId));
But it takes too much time to get tweets, is there any rate limit for this ? If there is a rate limit how can i make it faster ?
Updating every single tweet via showStatus wastes your "credits" for a given timeframe (rate-limit).
For updating multiple tweets, you should use lookup with a maximum of 100 ids per request. This call will use the /statuses/lookup endpoint.
Rate-Limit and endpoint documentation can be found here
Code-Snipped for it:
Twitter twitter = twitterFactory.getInstance();
ResponseList<Status> responseList = twitter.lookup(ArrayUtils.toPrimitive(ids));
if(responseList != null) {
for (Status status : responseList) {
// do what you need to do here
}
}
I am building a project where i want to extract a list from a query using firebase 3.0. I am quite new to this but I imagine that there is a simple answer to my question.
I have this structure :
requests : {
luke1 : {
1 : {
.../...
users : {
0 : {
username : joseph,
answered : 0
}
1 : {
username : mark,
answered : 1
}
}
}
}
}
Basically the logged in user (luke1) sends a request to a number of users (joseph and mark) and lets say i'm logged in as user: joseph.
I want to have get a list of requests sent to joseph which where not replied yet
var ref = firebase.database().ref("requests/");
i want to know how can i write the query.
Thanks for taking time to read this and if you need more information from my end, please let me know.
When using Firebase (and in most NoSQL databases), you will often find that you end up modeling the data for the way your app wants to consume it.
So with you current data model, you can easily get the requests sent by a specific user.
ref.child("requests/luke1").on("child_added", ...
But you cannot yet easily find the requests sent to a specific user. To allow querying for that data easily, you could add an inverted data structure to your database:
received: {
joseph: {
0: {
from: luke1
answered: 0
}
}
}
Now you can easily get joseph's unanswered requests with:
ref.child("received/joseph").orderByChild("answered").equalTo(0).on("child_added", ...
Your initial response is likely that this sort of data duplication is bad. But it's actually quite common in NoSQL databases.
There are many more ways to model this structure. For a great introduction to the topic, I recommend this article on NoSQL data modeling.
To achieve this kind of query you need to store in a variable the currentID of your user. After do this, just try something like the next query:
var ref = firebase.database().ref("request").child(currentUserId).child("users");
If I'm not wrong, it will return you the query that you want.
I am working on a web app project that has been in development for long time. The app has two sides, the majority of the site is publicly accessible. However, there are sections that require the user to be logged in before they can access certain content.
When the user logs in they get a sessionid (GUID) which is stored in a table in the database which tracks all sort for data about the user and their activity.
Every page of the app was written to look if this session id variable exists or not in the querystring. If a user tries to access one of these protected areas, the app checks to see if this sessiond variable is in the querystring. If i is not, they are redirected to the login screen.
The flow of the site moves has the user moving seamlessly from secured areas to non-secured areas, back and forth, etc.
So we did a test run with the Google Custom Search and it does an awesome job picking up all our dynamic content in these public areas. However, we have not been able to figure out how to pass the sessionid along with the search results IF the user is logged in already.
Is it possible to pas querystring variables that already exist in the url along with the search results?
As far as I know, this is not possible. Google doesn't give you the possibilty to modify the URL's of the Search Results in their Custom Search.
A possible solution would be to store your Session-Key to a Cookie, rather than passing it with every URL.
Use the parseQueryFromUrl function
function parseQueryFromUrl () {
var queryParamName = "q";
var search = window.location.search.substr(1);
var parts = search.split('&');
for (var i = 0; i < parts.length; i++) {
var keyvaluepair = parts[i].split('=');
if (decodeURIComponent(keyvaluepair[0]) == queryParamName) {
return decodeURIComponent(keyvaluepair[1].replace(/\+/g, ' '));
}
}
return '';
}
Select RESULTS ONLY option in the Look & Feel and it will provide you with the code.
www.google.com/cse/