I would like to setup geofence (center and radius, optionally path), specify the type of notifications I want to receive and get notifications when that happens. Something like that:
class Geofence
{
name;
CenterLocation;
radius;
}
var geofence=new Geofence(name.location,radius);
geofence.AddNotification(name, entered, enteredCallback); //or geofence.EnteredEvent +=event handler
geofence.AddNotification(name, exited, exitedCallback); //or geofence.ExitedEvent +=event handler
geofence.StartMonitoring();
geofence.StopMonitoring();
Or have that as static functions
Geofence.AddNotification(name or geofence, callback)
Geofence.StartMonitor(geofence);
The API can be done many ways, the point is to be able to get notifications when user enters and exits a geofence.
IOS support:
https://www.raywenderlich.com/136165/core-location-geofencing-tutorial
Android support:
https://developer.android.com/training/location/geofencing
VS bug #735669
It would really help if you could share following information when proposing new features
I would like to setup geofence (center and radius, optionally path), specify the type of notifications I want to receive and get notifications when that happens. Something like that:
class Geofence
{
name;
CenterLocation;
radius;
}
var geofence=new Geofence(name.location,radius);
geofence.AddNotification(name, entered, enteredCallback); //or geofence.EnteredEvent +=event handler
geofence.AddNotification(name, exited, exitedCallback); //or geofence.ExitedEvent +=event handler
geofence.StartMonitoring();
geofence.StopMonitoring();
Or have that as static functions
Geofence.AddNotification(name or geofence, callback)
Geofence.StartMonitor(geofence);
The API can be done many ways, the point is to be able to get notifications when user enters and exits a geofence.
IOS support:
https://www.raywenderlich.com/136165/core-location-geofencing-tutorial
Android support:
https://developer.android.com/training/location/geofencing
Geofence all the things
Integration Point: Geofences
API: Geofences
static class Geofences
/// <summary>
/// Current set of geofences being monitored
/// </summary>
IReadOnlyList<GeofenceRegion> MonitoredRegions { get; }
/// <summary>
/// Start monitoring a geofence
/// </summary>
/// <param name="region"></param>
void StartMonitoring(GeofenceRegion region);
/// <summary>
/// Stop monitoring a geofence
/// </summary>
/// <param name="region"></param>
void StopMonitoring(GeofenceRegion region);
/// <summary>
/// Stop monitoring all active geofences
/// </summary>
void StopAllMonitoring();
/// <summary>
/// This will request the current status of a geofence region
/// </summary>
/// <param name="region"></param>
/// <param name="cancelToken"></param>
/// <returns>Status of geofence</returns>
Task<GeofenceStatus> RequestState(GeofenceRegion region, CancellationToken? cancelToken = null);
/// <summary>
/// The geofence event
/// </summary>
event EventHandler<GeofenceStatusChangedEventArgs> RegionStatusChanged;
public enum GeofenceStatus
{
Unknown = 0,
Entered = 1,
Exited = 2
}
public class GeofenceStatusChangedEventArgs : EventArgs
{
public GeofenceRegion Region { get; }
public GeofenceStatus Status { get; }
}
public class GeofenceRegion
{
public string Identifier { get; }
public Position Center { get; }
public Distance Radius { get; }
}
Most of the implementation will be pulled from: https://github.com/aritchie/geofences/tree/dev
@aritchie I'm curious what the reason for the cancellationtoken is in RequestState... By that I mean, what kind of timing are we looking at for regions to be checked for status?
I have a number of other suggestions:
GeofenceRegion.Identifier should be IdGeofenceRegion.Radius should double RadiusInMetersGeofenceRegion.Center should either be a Location or perhaps we should just have CenterLatitude and CenterLongitude as I presume these are the only values considered for the fence center.GeofenceStatus to a property of GeofenceRegion ? This way there would be no additional call to query the current status? Or does one of the platforms make this impractical?GeofenceStatus should really be called GeofenceState - there may be other opinions here.@redth
PLEASE TELL me if you guys are really planning to take this on. I've been doing a ton of plugins lately, including a mass update of this thing which I was about to release since I thought this proposal was dead.
You still need a data store for android. Take a look at my dev branch. I'm using sqlite because that's my thing, but I can flat file/poor man serialize if necessary.
So iOS might not have had a GPS fix recently enough, but you might want to request state sooner to cause that to happen essentially? It's a bit weird that they are supposed to be letting us know we hit a fence but don't necessarily and can request updates on it?
I think Id still makes sense with other naming conventions. I think we should even prepopulate it with a Guid in the ctor so that it has an ID right away, and then make that read only.
Let's keep RadiusInMeters and keep it simple. We can convert on the backend if needed and we are adding a few helper unit converter methods in another class that can always have things added like FeetToMeters. We don't provide special types for things like Pressure anywhere else in the library, so we can be consistent here with that.
I'm on the fence about Position vs Lat/Lng... @jamesmontemagno any opinions? We could introduce a new Position type, but I don't feel like Location makes sense here due to the additional meaningless properties in this use case.
I was just thinking if it made sense for the GeofenceRegion itself to hold the property to the GeofenceState... But perhaps that introduces ambiguous behaviour about the current state if you hold a reference to the region object. Maybe it's best to leave this alone.
Another question: Platforms are limited in the number of fences they can track, yes? We should consider adding something to the API that helps with this. Perhaps we throw an exception once the max # is reached, and maybe provide a property so the user can see how many fences are available?
I'd like to take this PR, just need to iron out the details.
As for data storage... Can we just serialize to Preferences? We do that in some other places (VersionTracking).
GPS fixes can take time if a phone just came on and you're in a building. IN fact, it may not get a fix until you go outside. I launch an app, set a fence, but I have to know if I'm already in the danger zone. RequestState will just sit there on iOS in this case.
Please don't prepopulate these IDs. I set them to primary keys from other objects in my apps, so I know where to tag/stalk a user :)
For special types, you should really consider them - https://github.com/angularsen/UnitsNet - drop the mic ;)
GeofenceRegion is the request, the event should probably have the state.
Yes - iOS limits to 20 (including ibeacons). Android is 60 I believe, but that changes as often as a new support library/version turns up.
Data storage - serializing to prefs is fine for droid & uwp. iOS will cause grief here though. You suggested serializing to disk a few months back - that is still the best option imo.
Ok I guess having a RequestStateAsync makes enough sense.
I still want prepopulated Id but we can have a ctor overload which accepts an id too. That should satisfy your request.
Folks are welcome to pull in something like UnitsNet in their own app. We aren't going to use special unit types. The system APIs don't and that's where we are taking a direction from in this case.
Yep we will leave state in the event.
Are there APIs to find the fence limit? Or do we just need to throw sensible exceptions here?
Why would iOS give us grief here? If we store in a file we definitely need to be careful about where and what kind of cloud backup happens for the file.
I will add the Async. I keep hopping the postfix will go away in the near future ;)
Prepopulated Id - I think you'll be setting yourself up for failure here. Identification of the geofence is crucial to marking the event occurrence. I'm not saying this because of my personal needs. You really need to put this on the user from the start. All of the scheduled or notification style events force this identification to be set in the native apis (beacons, geofences, notifications, etc).
Units - That's fine. We'll agree to disagree on this one and my PR will reflect the decision, but I need to rant. I think there are 3 scales here 1. simplicity, 2. powerful, 3. complex. You guys are choosing 1 which I can appreciate, but you really lose your long term users and strong devs on this (I can blab about RX, but that will be a neverending debate that it powerful, not complex and the other ecosystems are using it). I feel like simplicity to get new user attraction is the wrong route to go.
Fence Limits - I have not found hard documentation, but it is well known to be 20 on iOS. The android general consensus is 100 it seems. Can't find a thing on UWP. I'll add a getter to the specification.
Data Storage and iOS - if you attempt to read/write to settings when a background event fires (usually after a reboot, but your app hasn't been launched), you'll start getting exceptions due to ProtectedDataDidBecomeAvailable ( https://stackoverflow.com/questions/27446235/cfprefrences-error-while-writing-to-nsuser-defaults-in-ios-when-app-have-data-pr ). Found this out the hard way in a few scenarios.
Once the kinks with storage and prepop Ids are worked out, I'll draft up another spec.
This looks like an interesting conversation and I think I come in the middle of @aritchie and @Redth...
Identifier property should be Id as this appears to be a .NET-y thingRadius and Center should really use existing types as we don't want to start implementing hundreds of little types for every case. Essentials should be simple and use built-in types.Center represent? latitude and longitude, then maybe we should re-use our location type? The extra properties are nullable, so I don't think it really matters, but that is just me.This is just me typing as I read, let me know what your thoughts are.
Perhaps the most interesting part of this is one we initially overlooked. We need a reasonable way to handle code execution started by a background service in Android. Since we essentially must pass a PendingIntent to the geofence manager in android, we need to get our callback in something like an IntentService. This means execution in the background when the app is not in the foreground.
We could have a static event however that would essentially require developers to implement their own Android Application and register to handle the event there. If you think about the lifecycle, most developers might register to the event in their Forms Application OnResume or something like that, which won't happen before the IntentService fires.
One approach we are discussing internally is to have some sort of abstract class for BackgroundTask executors that the developer would register the typeof(..) their own implementation with the API. We could then new up their type and run their implementation code in the background if a geofence event comes while the app is in the background.
We would also potentially want to expose an event still to be used if the app wants to do anything UI related when fence events happen in the foreground (something like GeofenceStateChangedInForeground that has a name which is obvious when it works).
It's not a completely pretty picture but I think we need to solve this problem properly. If you get a fence event in the background, you can't do anything UI related (aside from using System API's like notifications which might _cause_ something UI related to happen), so we really should structure the callback of this accordingly.
Thoughts?
The pending intent isn't hard to manage or document. I definitely have felt the pain of trying to tell people not to call UI things from an event that can fire from the background (ie. UserDialogs). However, I don't think there should be separate events for foreground vs background. The event hooks are more difficult to explain though.
I was thinking about tying geofences into my jobs plugin (exactly what you mentioned @Redth - https://github.com/aritchie/jobs ). Another option is to do an event queue with hot/cold events. When the event is hooked by something, flush out the queue although I may have different services in my app listening to the geofence event.
@mattleibow
the cancellation token - we have to be careful if there are platforms that cannot be cancelled.
I agree that we should probably not pre-populate the Id, as this is probably not consistent. I don't think we should go read-only as I may want to use a human-readable id. For example if I am doing this for my personal life, I may set up zones such as home, work, shop and a GUID is just a bit annoying... Since the ID is required, it should be a constructor augment and not let them create an instance without one - throw on null.
for serialization, we can make use of the platform feature and not do anything. Android has extras, iOS we can pipe the bits if need be (unless there is a max length) and UWP we can do the same. Just a random thought...
for the limits, what happens on the native platforms? if they throw, we can catch and then re-throw. I don't think we need/want to implement our own counting logic or something. This will cause issues if we aren't perfect - and is there any setting somewhere in the OS that will allow the user/another app to reset without us knowing?
@aritchie Your comment about the background events:
However, I don't think there should be separate events for foreground vs background.
What happens if I register a geofence and then I kill my app. when I enter the area, I need code to run. but, both the app and all that is associated is killed.
What should happen, no events are attached, the mono runtime is dead. The OS will wake the app and jump into the intent service - where do we go from here?
@mattleibow I actually agree with @Redth and I've already actually dealt with this (mostly) in my jobs plugin as mentioned above. We need to register a delegate, not an event - an actual type that we can create and send data through when bringing the app to life.
So the something like this Preferences.Set("GeofenceDelegate", typeof(MyGeofenceDelegate).AssemblyQualifiedName); The type will need to obviously inherit some type of interface so that the data can be sent to it.
Hehe, ok, so we all agree :)
Just on that, we could create a generic method like this:
void SetDelegate<T>()
where T : ITheDelegate
{
}
So right now this one is basically blocked by #409 and some similar discussion has happened in #290
Given https://github.com/xamarin/Essentials/issues/409 is still incomplete, I assume this hasn't seen an update recently. Would that be accurate?
Most helpful comment
Description of the Feature
Geofence all the things
API Declaration
Integration Point: Geofences
API: Geofences
Documentation links for supported platforms
Most of the implementation will be pulled from: https://github.com/aritchie/geofences/tree/dev