I'm currently developing a mobile app using AngularJS / Ionic 3.
I need to track at all time, if my users are within a radius of 100m of a certain geolocation (let's call it "Home Location". I need to know at all time which user is at his home location, and who is not (even when the app is running in background or is closed/ terminated).
I thought to realize this, using the Ionic Native Background-Geolocation plugin and the cordova-plugin-background-geolocation. My plan was, to check the users geolocation every 5 minutes and compare it to the home.
Case 1) If the distance between the two locations is < 100m I know the user is "at home". I would then update the user node in my database (Firebase) to mark the user as isAtHome: true and add a current timestamp.
Case 2) If the user is not within 100m of his home location I would mark him as isAtHome: false and add a current timestamp.
I need to update my database even in case the user didn't move, because I need to know that I received a current signal. Otherwise I don't know if he didn't move or turned off his smartphone, for example.
If I want to know, who of my users is at his home location, I would check the isAtHome-attributes and if they are set to true, I would check the timestamp to be sure that I have up-to-date data and the data was written within the last 15 minutes.
I tried many different things, but I'm not sure how to realize this with the Cordova Background Geolocation Plugin and if it's even possible.
My question is:
Is it possible to check the users geolocation every 5 minutes (even in background / terminated state) and call a function that updates my firebase DB accordingly? As described, I need to receive the location even if the user didn't move within the last 5 minutes.
If it isn't possible: Does anybody have an idea, of how to approach the requirements on another way?
Thank you very much for your help!
As described in the cordova-plugin-background-geolocation says, you can configure an interval in milliseconds for the plugin to get the location, see this example from the plugin github page:
backgroundGeolocation.configure(callbackFn, failureFn, {
desiredAccuracy: 10,
stationaryRadius: 20,
distanceFilter: 30,
url: 'http://192.168.81.15:3000/locations',
httpHeaders: { 'X-FOO': 'bar' },
maxLocations: 1000,
// Android only section
locationProvider: backgroundGeolocation.provider.ANDROID_ACTIVITY_PROVIDER,
interval: 60000, // <-- here
fastestInterval: 5000,
activitiesInterval: 10000,
notificationTitle: 'Background tracking',
notificationText: 'enabled',
notificationIconColor: '#FEDD1E',
notificationIconLarge: 'mappointer_large',
notificationIconSmall: 'mappointer_small'
});
The case is this interval property works only for Android. I would sugest a workaround like this:
public getLocation: boolean = true; // CREATE A BOOLEAN TO KNOW IF YOU CAN GET LOCATION
backgroundGeolocation.start( // i've never used this plugin, so i'm assuming this is what starts de background watcher
() => {
if(getLocation == true){
this.getLocation = false; // SET TO FALSE SO IT DON'T DO YOUR FUNCTION THE NEXT TIME IT GETS THE LOCATION
// DO YOUR CODE TO SAVE AND CHECK YOUR DATA
// IT'S BETTER IF YOU USE A PROMISE, ASYNC/AWAIT FOR THE SAVING CODE
yourSavingCodeFunction().then(() => {
setTimeout(()=>{
// AFTER 5 MINUTES, SET THE GETLOCATION TO TRUE SO IT CAN DO YOUR FUNCTION AGAIN
this.getLocation = true;
}, 500000);
});
}
},
(error) => {
// Tracking has not started because of error
// you should adjust your app UI for example change switch element to indicate
// that service is not running
}
);
If you haven't thought about your cases 1 and 2 we can work on this too. Hope this helps or can give you some ideas :D
Related
I'm completely new to SalesForce and have inherited a report that's not working. Please excuse any incorrect terminology, since I'm learning about all this as I go. The report has three prompts: states, years, and members. All dropdowns are supposed to populate with data returned from functions in an APEX class. State, which populates from a picklist, and years, which is populated with a loop, work fine. Members, which populates from a SQL query, returns nothing. If I run the report without any prompts selected (which should return an unfiltered list of results from a SQL query), it also returns nothing. Both of the SQL queries return data when I execute them directly in the query editor in the developer console, but they return nothing when called from the APEX functions.
Here's the initialization code from the Lightning controller:
doInit: function (component, event, helper) {
var action = component.get('c.getTrcAccounts');
action.setCallback(this, function (response) {
var state = response.getState();
if (state === 'SUCCESS' && component.isValid()) {
component.set('v.trcAccList', response.getReturnValue());
}
helper.getLocationState(component, event);
helper.getYear(component, event);
});
$A.enqueueAction(action);
},
Here are the two helper functions referenced in that code:
getLocationState: function (component, event) {
var action = component.get('c.getLocationState');
action.setCallback(this, function (response) {
var state = response.getState();
if (state === 'SUCCESS') {
component.set('v.LocationStateList', response.getReturnValue());
}
});
$A.enqueueAction(action);
},
getYear: function (component, event) {
var action = component.get('c.yearsOptions');
action.setCallback(this, function (response) {
var state = response.getState();
if (state === 'SUCCESS') {
component.set('v.LocationYearList', response.getReturnValue());
}
});
$A.enqueueAction(action);
}
Here is the code from the APEX class that returns the data for those three prompts:
Global class DataTableLocations {
#AuraEnabled
Global static List<TRC_Account__c> getTrcAccounts(){
set<string> trcAccountSet = new set<string>();
List<TRC_Account__c> traccList = new List<TRC_Account__c>();
for(TRC_Account__c trcacc : [SELECT Id, Name from TRC_Account__c WHERE TRC_Member__c = True order by Name limit 50000]){
if(!trcAccountSet.contains(trcacc.Name)){
trcAccountSet.add(trcacc.Name);
traccList.add(trcacc);
}
}
if(traccList.size()>0){
return traccList;
}
else{
return null;
}
}
#AuraEnabled
Global static List<string> getLocationState(){
List<string> options = new List<string>();
//options.add(new SelectOption('SelectAll', 'Select All'));
for( Schema.PicklistEntry f : Location__c.Physical_Address_State__c.getDescribe().getPicklistValues()) {
options.add(f.getValue());
}
return options;
}
#AuraEnabled
Global static List<string> yearsOptions() {
List<string> options = new List<string>();
date OldDate= date.today().addYears(-18);
integer oldyear=OldDate.year();
for( integer i=0; i<19 ;i++) {
options.add(string.valueOf(oldyear));
oldyear++;
}
return options;
}
}
If I run SELECT Id, Name from TRC_Account__c WHERE TRC_Member__c = True order by Name limit 50000 directly in the query editor window in the developer console, I get 7 results. However, if I output the response.getReturnValue() for getTrcAccounts in the doInit function, it's null.
Any help is greatly appreciated, as we're in a bit of a time crunch in conjunction with a site redesign. I'm told these reports were working at one point, but no one knows when they stopped working, and we inherited this code from a different company that did the original development. Thank you!
UPDATE:
In case it helps, this is the code in the lightning app that I think is used on the public page:
<aura:application extends="ltng:outApp" access="GLOBAL" implements="ltng:allowGuestAccess">
<aura:dependency resource="c:SearchBinReceiptsByYear"/>
</aura:application>
Edit
Right, it's a public page, it's called "Salesforce Sites". It's exposed to whole world without having to log in. These have special security in place because most of the time you don't want to expose data like that. At best you'd display contact us form, maybe some documents to download, product catalog... It's all very locked down, default is to ban everything and then admin decides what's allowed. It's bit unusual to have a Visualforce page + Aura component but ok, it happens.
You (and any other internal user) can see the results if you'd access this page from within salesforce. Something like https://mydomain.my.salesforce.com/apex/SearchBinReceiptsByYear and for you the page will work fine, "just" not outside of salesforce.
When exposed like that on the web - there's no logged in user. There's special "[Site Name] Guest User", you can see them if you search "Sites" in Setup. It has a special profile, also with [Site Name] in it. And nasty thing is - it doesn't show on the list of Users or Profiles.
Your code broke when Salesforce (auto)activated a critical update. Probably this one: https://releasenotes.docs.salesforce.com/en-us/spring20/release-notes/rn_networks_secure_perms_guests.htm There are some good resources on the net if you Google "Secure Object Permissions for Guest Users", for example https://katiekodes.com/salesforce-spring-20-guest-user/
Ask your system administrator colleague or read up a bit about sharing rules.
You'll have to go to Setup -> Sharing Rules. There's a checkbox that caused your stuff to break and you can't untick it.
Scroll down to your TRC Account object and hit "New". You'll need to create something like this, but with your criteria (TRC Member equals true)
Save, wait a bit (it might take a while to recalculate the sharing, you'll get an email) and try the page.
If it still doesn't work you'll have to check the Guest user's profile, it might need permissions to Read TRC Accounts and their Name field.
If it's Salesforce Sites - try this to find it: https://help.salesforce.com/articleView?id=000334554&type=1&mode=1
If it's a Customer Portal, Community, Digital Experience (they renamed the product few times) - try with https://help.salesforce.com/articleView?id=sf.rss_config_guest_user_profile.htm&type=5
Original answer
It looks like it's running OK because accounts (members?) are fetched first and in that fetch's callback (what to do when data comes back from server) you have helper.getLocationState, helper.getYear. And you wrote that these populate OK. It's not the best performance code but it should get the job done.
In no specific order...
Does the whole thing work OK for sysadmins? Or is it broken for everybody? If it works for sysadmins it might be something to do with sharing, your sysadmin should know (Setup -> Sharing settings is where you control who can see what. Maybe "mortals" are not allowed to see any data? Typically sysadmins bypass it. As a quick & dirty test you can modify the class definition to global without sharing class DataTableLocations but it's a really ugly hack.
What happens if you open DeveloperConsole (upper right corner) while running this component, do you see any errors in the logs? What happens if in the console you go Debug -> Open ExecuteAnonymous and run this piece of code:
System.debug(DataTableLocations.getTrcAccounts());
Does it return something? Throw error?
You can go to Setup -> Debug Mode, tick the checkbox next to your user and save. This slows the system down a bit but lets you debug the javascript better. You can then sprinkle some debugger; or console.log statements in the source code and view what happens in your browser's console (Ctrl+Shift+J in Chrome, Ctrl+Shift+I in firefox). For example
action.setCallback(this, function (response) {
var state = response.getState();
debugger;
console.log(state);
console.log(component.isValid());
console.table(response.getReturnValue());
if (state === 'SUCCESS' && component.isValid()) {
component.set('v.trcAccList', response.getReturnValue());
}
console.log(component.get('v.trcAccList'));
debugger;
helper.getLocationState(component, event);
helper.getYear(component, event);
});
How's the trcAccList variable actually used in the "cmp" file, in the HTML-like file? Maybe it's being set all right and contains 7 records but it's not displayed right?
Good day,
Let me describe my problem :
Scenario :
User 1 has a coldfusion webpage, containing 6 alerts as html elements populated using a database. User 1 is constantly monitoring this page for changes.
A user on a different computer (different session) logs into a admin console and add's a 7th element and inserts it into the database.
The database contains a field that changes from 0 to 1 as soon as an alert is added and inserted into the database.
The webpage of User 1 not has to dynamically refresh or update with the 8th alert.
Problem :
I am struggling to run a async loop that queries the database permanently for the record that tells it there is a change, while not freezing the rest of the page.
Solution :
I need to run a cfquery to check the isUpdated field in the database.
That cfquery needs to be run every minute.
As soon as the cfquery returns a 1, the page should refresh which in turn will populate the new alert as well.
Can be done through sockets, and you do not have to check the availability of a new record every minute.
You can also do it with javascript/jquery:
<script>
var lastAlert = '#lastAlert#'; //last at the time of loading the page
setInterval(function() {
lastAlert = isUpdated(lastAlert);
}, 60000);
function isUpdated(lastAlert){
res = lastAlert;
$.ajax({
url: 'checkAlerts.cfm', //in this file do check for changes in the database. should return the number of the last alert
type: 'POST',
data: {lastAlert:lastAlert},
cache: false,
success:function(res){
if(res > lastAlert){
//your code if there is a new entry
alert('a new entry has been added');
}
}
});
return res;
}
</script>
I did not check the code! But I hope you understand how to proceed.
Okay. I'm kinda new to react and I'm having a #1 mayor issue. Can't really find any solution out there.
I've built an app that renders a list of objects. The list comes from my mock API for now. The list of objects is stored inside a store. The store action to fetch the objects is done by the components.
My issue is when showing these objects. When a user clicks show, it renders a page with details on the object. Store-wise this means firing a getSpecific function that retrieves the object, from the store, based on an ID.
This is all fine, the store still has the objects. Until I reload the page. That is when the store gets wiped, a new instance is created (this is my guess). The store is now empty, and getting that specific object is now impossible (in my current implementation).
So, I read somewhere that this is by design. Is the solutions to:
Save the store in local storage, to keep the data?
Make the API call again and get all the objects once again?
And in case 2, when/where is this supposed to happen?
How should a store make sure it always has the expected data?
Any hints?
Some if the implementation:
//List.js
componentDidMount() {
//The fetch offers function will trigger a change event
//which will trigger the listener in componentWillMount
OfferActions.fetchOffers();
}
componentWillMount() {
//Listen for changes in the store
offerStore.addChangeListener(this.retriveOffers);
}
retrieveOffers() {
this.setState({
offers: offerStore.getAll()
});
}
.
//OfferActions.js
fetchOffers(){
let url = 'http://localhost:3001/offers';
axios.get(url).then(function (data) {
dispatch({
actionType: OfferConstants.RECIVE_OFFERS,
payload: data.data
});
});
}
.
//OfferStore.js
var _offers = [];
receiveOffers(payload) {
_offers = payload || [];
this.emitChange();
}
handleActions(action) {
switch (action.actionType) {
case OfferConstants.RECIVE_OFFERS:
{
this.receiveOffers(action.payload);
}
}
}
getAll() {
return _offers;
}
getOffer(requested_id) {
var result = this.getAll().filter(function (offer) {
return offer.id == requested_id;
});
}
.
//Show.js
componentWillMount() {
this.state = {
offer: offerStore.getOffer(this.props.params.id)
};
}
That is correct, redux stores, like any other javascript objects, do not survive a refresh. During a refresh you are resetting the memory of the browser window.
Both of your approaches would work, however I would suggest the following:
Save to local storage only information that is semi persistent such as authentication token, user first name/last name, ui settings, etc.
During app start (or component load), load any auxiliary information such as sales figures, message feeds, and offers. This information generally changes quickly and it makes little sense to cache it in local storage.
For 1. you can utilize the redux-persist middleware. It let's you save to and retrieve from your browser's local storage during app start. (This is just one of many ways to accomplish this).
For 2. your approach makes sense. Load the required data on componentWillMount asynchronously.
Furthermore, regarding being "up-to-date" with data: this entirely depends on your application needs. A few ideas to help you get started exploring your problem domain:
With each request to get offers, also send or save a time stamp. Have the application decide when a time stamp is "too old" and request again.
Implement real time communication, for example socket.io which pushes the data to the client instead of the client requesting it.
Request the data at an interval suitable to your application. You could pass along the last time you requested the information and the server could decide if there is new data available or return an empty response in which case you display the existing data.
On first sign-up I am doing a full sync for the last 50 threads in label with id INBOX.
How should I go about implementing a "load more" feature, where the user can say I would like to fetch the next 50 threads. As far as I see there are 2 possible ways to go about it:
Cache nextPageToken from initial full sync and use that to load next 50 (maxResults = 50)
Use the q parameter with older and newer - this works well for dates however I could not find if this works for absolute time.
Neither of them works for my use case in which I specifically would like to get the next 50 threads older or all threads newer than this point of time.
I would like to do this because if I fetch threads per label, and in my data model labels and threads have a many-to-many relationship, I will have date gaps in the different labels.
Here is an example: I go into a label that has messages from 2009, I fetch them. They are also in Inbox so if I go there I will see emails from October 2014 and then suddenly September 2009. My solution would be to fetch threads from All Mail newer than the oldest thread whenever I do load more or initial full sync to make sure there are no date gaps.
Also to save bandwidth, is it possible to include in the request the thread ids I already have, to not be returned in the response?
I don't think you need to overcomplicate things. If you don't do any newer, older or specific sorting the messages are ordered by date desc. For the pages I created a simple array to hold all the page tokens. Quite easy and works well (AngularJS example):
/*
* Get next page
*/
$scope.fetchNextPage = function() {
if ($scope.nextPageToken) {
$scope.page++;
$scope.pageTokenArray[$scope.page] = $scope.nextPageToken;
$scope.targetPage = $scope.pageTokenArray[$scope.page];
$scope.fetch(false);
// As we have a next page, always allow
// to go back
$scope.lastBtnDisabled = false;
}
};
/*
* Get previous page
*/
$scope.fetchLastPage = function() {
if ($scope.page > 0) {
$scope.page--;
$scope.targetPage = $scope.pageTokenArray[$scope.page];
$scope.fetch(false);
// When page is 0 now, disable last page
// button
if ($scope.page == 0) {
$scope.lastBtnDisabled = true;
} else {
$scope.lastBtnDisabled = false;
}
}
};
I'm developing a web application using GeoExt, OpenLayers and having my own GeoServer to serve various maps. Still, I want to let the user add other WMS's if needed, to be able to play around with all desired layers.
Thus, my problem with the GetFeatureInfo request. Right now I have a toolbar button attached to geoext's map panel,
new GeoExt.Action({
iconCls: "feature",
map: map,
toggleGroup: "tools",
tooltip: "Feature",
control: featureControl
})
its control attribute being
var featureControl = new OpenLayers.Control.WMSGetFeatureInfo({
queryVisible: true,
drillDown: true,
infoFormat:"application/vnd.ogc.gml"
});
I've also defined an event listener to do what I really want once I receive the responses, but that is not relevant here. My problem is the following:
Considering the user clicks on a point where there are 2+ visible layers and at least one of them is from a different source, OpenLayers will have to do one AJAX request per different source and, from OpenLayers own documentation,
Triggered when a GetFeatureInfo response is received. The event
object has a text property with the body of the response (String), a
features property with an array of the parsed features, an xy property
with the position of the mouse click or hover event that triggered the
request, and a request property with the request itself. If drillDown
is set to true and multiple requests were issued to collect feature
info from all layers, text and request will only contain the response
body and request object of the last request.
so, yeah, it will obviously wont work like that right away. Having a look at the debugger I can clearly see that, giving two layers from different sources, it actually DOES the request, it's just that it doesn't wait for the first's response and jumps for the next one (obviously, being asynchronous). I've thought about doing the requests one-by-one, meaning doing the first one as stated above and once it's finished and the response saved, go for the next one. But I'm still getting used to the data structure GeoExt uses.
Is there any API (be it GeoExt or OpenLayers) option/method I'm missing? Any nice workarounds?
Thanks for reading :-)
PS: I'm sorry if I've not been clear enough, english is not my mother tongue. Let me know if something stated above was not clear enough :)
i Hope this help to someone else, I realized that: you're rigth this control make the request in asynchronous mode, but this is ok, no problem with that, the real problem is when the control handle the request and trigger the event "getfeatureinfo" so, i modified 2 methods for this control and it works!, so to do this i declare the control first, and then in the savage mode i modified the methods here is de code:
getInfo = new OpenLayers.Control.WMSGetFeatureInfo({ drillDown:true , queryVisible: true , maxFeatures:100 });
//then i declare a variable that help me to handle more than 1 request.....
getInfo.responses = [];
getInfo.handleResponse=function(xy, request) { var doc = request.responseXML;
if(!doc || !doc.documentElement) { doc = request.responseText; }
var features = this.format.read(doc);
if (this.drillDown === false) {
this.triggerGetFeatureInfo(request, xy, features);
} else {
this._requestCount++;
this._features = (this._features || []).concat(features);
if( this._numRequests > 1){
//if the num of RQ, (I mean more than 1 resource ), i put the Request in array, this is for maybe in a future i could be need other properties or methods from RQ, i dont know.
this.responses.push(request);}
else{
this.responses = request;}
if (this._requestCount === this._numRequests) {
//here i change the code....
//this.triggerGetFeatureInfo(request, xy, this._features.concat());
this.triggerGetFeatureInfo(this.responses, xy, this._features.concat());
delete this._features;
delete this._requestCount;
delete this._numRequests;
// I Adding this when the all info is done 4 reboot
this.responses=[];
}
}
}
getInfo.triggerGetFeatureInfo= function( request , xy , features) {
//finally i added this code for get all request.responseText's
if( isArray( request ) ){
text_rq = '';
for(i in request ){
text_rq += request[i].responseText;
}
}
else{
text_rq = request.responseText;
}
this.events.triggerEvent("getfeatureinfo", {
//text: request.responseText,
text : text_rq,
features: features,
request: request,
xy: xy
});
// Reset the cursor.
OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");}
Thanks, you bring me a way for discover my problem and here is the way i solved, i hope this can help to somebody else.
saheka's answer was almost perfect! Congratulations and thank you, I had the same problem, and with it I finally managed to solve it.
What I would change in your code:
isArray() does not work, change it like this: if(request instanceof Array) {...} at the first line of getInfo.triggerGetFeatureInfo()
to show the results in a popup this is the way:
My code:
getInfo.addPopup = function(map, text, xy) {
if(map.popups.length > 0) {
map.removePopup(map.popups[0]);
}
var popup = new OpenLayers.Popup.FramedCloud(
"anything",
map.getLonLatFromPixel(xy),
null,
text,
null,
true
);
map.addPopup(popup);
}
and in the getInfo.triggerGetFeatureInfo() function, after the last line, append:
this.addPopup(map, text_rq, xy);
A GetFeatureInfo request is send as a JavaScript Ajax call to the external server. So, the requests are likely blocked for security reasons. You'll have to send the requests to the external servers by a proxy on your own domain.
Then, configure this proxy in openlayers by setting OpenLayers.ProxyHost to the proper path. For example:
OpenLayers.ProxyHost = "/proxy_script";
See http://trac.osgeo.org/openlayers/wiki/FrequentlyAskedQuestions#ProxyHost for more background information.