Get current location from the geo fence sample project - geolocation

I was trying out the Google's Geo fence sample project. I'm getting the entry/exit events correctly. But how can I get the current location of the user from it. I would like to keep track of user location even after entry/exit points. Please help.
The sample project is from the developer portal.

in your broadcastrecievero or service you get a GeofencingEvent , use it to get the triggering position
public class GeofenceTransitionsIntentService extends IntentService {
//[...]
#Override
protected void onHandleIntent(Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()) {
// ...
}
Location l = geofencingEvent.getTriggeringLocation()
}
//[...]
}

You'll need can get the user location from the Fused Location Provider in the normal fashion. The full tutorial is here: http://developer.android.com/training/location/retrieve-current.html
But basically, when you receive the geofence event inside your Receiver or Service, get an instance of LocationClient there, connect to it, and in the callback, onConnected(), get the last known location:
Location position = locationClient.getLastLocation();
99.9% of the time, or a little better, you'll find this location to be inside your geofence.
To continue keeping track of user location updates, follow the Receiving Location Updates: http://developer.android.com/training/location/receive-location-updates.html

Related

Function that fires after user's location has updated

Important Note: I am aware that I could simply call my callback function inside didUpdateLocations and achieve what I want. Unfortunately, this route cannot be taken because of some pre-existing design decisions that were made in this project (which I have no influence over).
I need to write a function that fires the first time the user's coordinates update, and then passes those coordinates to a completion handler. In my case, that completion handler is a function called fetchCountry(fromLocation: CLLocation) which returns the country corresponding to the CLLocation given.
In other words, I want to write a function similar to didUpdateLocations, with the capability of calling a completion handler after those updates have been received:
func getUserLocation(callback: #escaping (CLLocation?) -> Void) {
// wait until user's location has been retrieved by location manager, somehow
// prepare output for callback function and pass it as its argument
let latitude = manager.location!.coordinate.latitude
let longitude = manager.location!.coordinate.longitude
let location = CLLocation(latitude: latitude, longitude: longitude)
callback(location)
}
In short, getUserLocation is just a wrapper for didUpdateLocations but I am really not sure how I would go about writing this function so that it achieves what I want.
My greater goal here is to show the user a local notification only if they are in a certain country (e.g. United States) upon launching the app. It is a hard requirement for my application to make the decision of scheduling/not scheduling this notification inside AppDelegate.swift, but this decision cannot be made until the user's location has been retrieved. I plan to use getUserLocation inside the appDelegate like this:
I hope that I have conveyed clearly that I am looking to achieve this using a function with a completion handler. Here is what I would like my code to do (i.e. my use case), inside AppDelegate.swift:
// inside didFinishLaunchingWithOptions
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in
if granted {
// this is the use case of the function I am trying to write
LocationManager.shared.getLocation(completion: { location in
// fetches the country from given location using reverse geo-coding
LocationManager.shared.fetchCountry(from: location, completion: { country in
if country == "United States" {
let notification = LocalNotification()
notificationManager.schedule(notification: notification)
}
})
})
}
}
Edited the whole answer. You would need to use a synchronizing api (OperationQueue, DispatchQueue, etc) because your CLLocationManager is already fetching even before getUserLocation is called. Callbacks alone can't handle this that's why I removed that option already. For this case, I used DispatchQueue because I prefer using it, to each their own.
class LocationManager: NSObject, CLLocationManagerDelegate{
static let shared: LocationManager()
private let privateQueue = DispatchQueue.init("somePrivateQueue")
private var latestLocation: CLLocation!{
didSet{
privateQueue.resume()
}
}
func getUserLocation(queue: DispatchQueue, callback: #escaping (CLLocation?) -> Void) {
if latestLocation == nil{
privateQueue.suspend() //pause queue. wait until got a location
}
privateQueue.async{ //enqueue work. should run when latestLocation != nil
queue.async{ //use a defined queue. most likely mainQueue
callback(self.latestLocation)
//optionally clear self.latestLocation to ensure next call to this method will wait for new user location. But if you are okay with a cached userLocation, then no need to clear.
}
}
}
func fetchCountry(from currentLocation: CLLocation, completion: ) //you already have this right?
#objc func locationManager(_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]){
latestLocation = locations.last! //API states that it is guaranteed to always have a value
}
}
I also agree on using a 3rd party library if possible. Those code would be guaranteed to be unit-tested and handle edge cases. Whereas, my above code could have a bug (none that I know of btw)
From your AppDelegate code I can assume that you are determining the country in the LocationManager class only. I would suggest to remove the call back from the getUserLocation() function and create a different function named postLocalNotification() in the AppDelegate to just post the local notification.
When you start fetching the user location the didUpdateLocation will be called in which you should call the fetchCountry() with the latest location. If the fetched country is proper and you want to post the local notification get the appelegate object and call the function which will post the notification as below
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.postLocalNotification()
Hope this helps.
Did you try to use PromiseKit+CoreLocation?
It provides CLLocationManager.requestLocation(authorizationType:satisfying:). If you don't want to import all the PromiseKit framework (which is great & avoid such completion chain), you can copy its code. They did exactly what you want: wrapping the CoreLocation request in a function.
I don't know what your special requirements are so this is general advice.
It sounds like you should set up a second location manager for your own use. Set it up in the app delegate and put its delegate callbacks in there, separate from the main location manager.
Don't try to delay willFinishLaunchingWithOptions from finishing. Depending on your requirements you might have to move any UI setup code to your own callback to set up the interface after the country is determined. I would even consider showing a different UI while you're doing this location and notification set up, then swap it out for the main UI when your have notification permission, location permission and the country.
Realise that the first location update you get through didUpdateLocations can be inaccurate. Check its distance accuracy but also check its timestamp and discard any update that's old. For your purposes (country accuracy) that probably means older than an hour. You're only really considering the use case where your app was opened for the first time after a user gets off a plane coming from another country. For accuracy, if the timestamp is recent, anything under 3000m or 5000m will be fine for that level.
Because the required accuracy is so low the location will be coming from cell tower triangulation. It should be fast (maybe within 2-5 seconds).
The one thing I'd be careful about is that your location manager will have to request location permissions while the main location manager does the same thing. I don't know how requesting permissions twice like that works.
I'd also separate fetching the country and getting location from the notification permissions.
Some more general advice: Your use case looks like it's handled in the WWDC session for Advanced NSOperations. The speaker handles cases where you need several things to be set up before the next part can move on. There's a location use case and a permissions use case in there too, one depending on the other.

Monitoring for more than 20 BLE beacon regions using CLLocationmanager in iOS

Currently I am using CLLocationmanager to monitor for BLE beacon regions in iOS.
I know I can range beacons if i want more than 20 regions but unfortunately ranging would not allow me to register entry(RegionDidEnter) and exit(RegionDidExit) events as far as I know.
In my use case I need to trigger actions on user's entry and user's exit in a particular beacon region even when app is in killed state or in background.
I need a efficient way to do this as if I look for significant location changes it also uses battery and also using beacons would not make much sense then if i use GPS.
When didEnter happens, iOS will launch your app into the background and give it a few seconds of execution time to handle the event. You can use that time to start ranging, receive the ranging results, and since ranging always provides full UUID/major/minor info, trigger an appropriate action based on that.
Pseudo-code:
let myUUID = x
startMonitoring(myUUID)
func onDidEnter {
startRanging(myUUID)
}
func onDidRange(beacons) {
if beacons.empty { return } // keep ranging until we find something
let major = beacons.first.major
if major == 1 { show("Welcome to X") }
if major == 2 { show("Welcome to Y") }
stopRanging(myUUID)
}
To ensure that your app doesn't get put back to sleep before it manages to range a beacon, you can also use a background task, then the (pseudo-)code would look something like:
func onDidEnter {
self.task = beginBackgroundTask(expirationHadler: {
// our background time is up, iOS requires us to finish our work
stopRanging(myUUID)
endBackgroundTask(self.task)
})
startRanging(myUUID)
}
func onDidRange(beacons) {
if beacons.empty { return }
let major = beacons.first.major
if major == 1 { show("Welcome to X") }
if major == 2 { show("Welcome to Y") }
stopRanging(myUUID)
endBackgroundTask(self.task)
}
You can add a workaround to this. Register only those regions near to the user location. When the location changes, you can remove regions that are now farther way and add regions coming up on the user’s path.
To save battery when dealing with location, register for significant-change location updates or make use of defer location updates or use visit monitoring.
Why Core Location limited to 20
Regions are a shared system resource, and the total number of regions
available systemwide is limited. For this reason, Core Location limits
to 20 the number of regions that may be simultaneously monitored by a
single app. To work around this limit,

Completion block for GPS coordinates

As my apps enables the user to get their location every now and then, I could really use the ability to get the location in a completion block. At the moment I've set up a notification using
NSNotificationCenter.defaultCenter().postNotificationName("functionToRun", object: nil)
When the user request the location, I just run this function
func getLocation() -> CLLocation {
locationManager.requestLocation()
return location
}
However this is updating the location which can take a while, and just returning the latest location, can I implement this in a completion block, so I can get the actual location?
CLLocationManager doesn't exactly work this way but you could easily use something like the CLLocationManager-blocks cocoa pod to get this functionality. Just read through the documentation to make sure it behaves the way you expect it to.

location based alarm/notification ios

I'm building an iOS app, however I want to get notification which may be a local and simple notification, such that on defined area of location, such that to provided the latitude and longitude to get the area around 200meter and when the user entered in that location, it alerts the notification.
How can I schedule the location based local notification in iOS.
Take a look at what is available in the SetSDK, https://cocoapods.org/pods/SetSDK. It will allow you to get notified whenever the user arrives to a new "location" (as learned on the fly by the sdk) and then just quickly evaluate to see if it is a location you care about. So it would look something like this,
SetSDK.instance.onArrival(to: .any) { newArrival in
/* Compare the new location with the one of interest */
if newArrival.location.distance(from: placeOfInterest) < 200 {
/* do your things here */
}
}
Use a CLRegion in combination with Background Location and your app will be woken up when the user enters that region. From there you can schedule a UILocationNotification for immediate display.

Blackberry JDE GPS getLocation() LocationException

I need to add GPS functionality to an existing Blackberry Application that I've written. I write a stand alone class called CurrentLocation, and include a method to set the various location variables I care about by using the blackberry GPS in conjunction with google's reverse geocoding webservices. Everything is working beautifully, that is, until I try to instantiate my new class in my main application.
No matter what I do, I get a LocationException! .getLocation() doesn't work!
It really confuses me, because if I instantiate my class in a test hello world app, it works just fine.
Are there limits to where you can instantiate classes? I've not encountered any with previous classes I've written. In this case, I'm instantiating my CurrentLocation class in a listener (so the user only makes the lengthy gps and web calls when they want to). I've tried instantiating it in screens, as well. I've tried just gutting the class and using the method call, but that doesn't work either.
Is there something I'm missing entirely here?
http://pastie.org/639545
There's a link to the class I'm making,
And here's the listener I"m trying to instantiate from. I'm in an event thread because I thought it might help (but I get the same exception whether or not I do this).
FieldChangeListener listenerGPS = new FieldChangeListener() {
public void fieldChanged(Field field, int context) {
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
CurrentLocation loc = new CurrentLocation();
if (loc != null){
country = loc.getCountry();
city = loc.getCity();
state = loc.getState();
road = loc.getRoad();
zip = loc.getZip();
}
}
});
}
};
What am I missing here?
Okay, I got it. Apparently you can't call getLocation() in the eventThread (not just invokeLater, but any listener). So now what I'm doing is getting the coordinates in a thread outside of the event, and worrying about google separately.

Resources