Xamarin iOS - Handling Geofence intersection triggers after terminating an app - ios

I am trying to handle geofence intersection triggers when my app is terminated (not background). I want handle the enter/exit triggers from CLLocationManager even if my app is not running. It should wake up my app at the background, do the required processing on enter/exit.
To do this, a lot of places its given that it is possible using background app refresh functionality. I have the following code written but as soon as i terminate the application, it stops hearing the geofence trigger events.
Can anybody please guide me how to handle these events even if application is terminated ?
public async Task StartLocationUpdates()
{
_cts = new CancellationTokenSource();
_taskId = UIApplication.SharedApplication.BeginBackgroundTask("LongRunningTask", OnExpiration);
try
{
if (CLLocationManager.LocationServicesEnabled)
{
LocMgr.DesiredAccuracy = 1;
LocMgr.LocationsUpdated += (object sender, CLLocationsUpdatedEventArgs e) =>
{
var locations = e.Locations;
};
LocMgr.StartUpdatingLocation();
if (CLLocationManager.LocationServicesEnabled)
{
if (CLLocationManager.Status != CLAuthorizationStatus.Denied)
{
if (CLLocationManager.IsMonitoringAvailable(typeof(CLCircularRegion)))
{
LocMgr.DidStartMonitoringForRegion += (o, e) =>
{
Console.WriteLine("Now monitoring region {0}", e.Region.ToString());
};
LocMgr.RegionEntered += (o, e) =>
{
Instance.Speak("Just entered " + e.Region.ToString());
};
LocMgr.RegionLeft += (o, e) =>
{
Instance.Speak("Just left " + e.Region.ToString());
};
foreach (CLCircularRegion region in RegionList)
{
if (region != null)
{
StopMonitoringRegion(region);
}
LocMgr.StartMonitoring(region);
}
}
else
{
Console.WriteLine("This app requires region monitoring, which is unavailable on this device");
}
LocMgr.Failed += (o, e) =>
{
Console.WriteLine(e.Error);
};
}
else
{
Console.WriteLine("App is not authorized to use location data");
}
}
else
{
Console.WriteLine("Location services not enabled, please enable this in your Settings");
}
}
}
catch (OperationCanceledException)
{
}
}
Thank You in advance.

It seems you have added the privacy keys in the info.plist to enable the location service. If you want to enable it when the app is on background state or terminated, we also should turn on the Background Modes: Capabilities > Background Modes > Location updates.
Then we can initialize the location manager in AppDelegate:
CLLocationManager locationManager;
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
locationManager = new CLLocationManager();
locationManager.Delegate = new MyLocationDelegate();
//enable service when app is on background or terminated
locationManager.PausesLocationUpdatesAutomatically = false;
locationManager.AllowsBackgroundLocationUpdates = true;
locationManager.RequestAlwaysAuthorization();
locationManager.DistanceFilter = 100.0;
locationManager.DesiredAccuracy = 1;
// The important request can awake your app
locationManager.StartMonitoringSignificantLocationChanges();
locationManager.StartUpdatingLocation();
return true;
}
The event LocationsUpdated() will also fire if your location has been changed even your app has been terminated:
public class MyLocationDelegate : CLLocationManagerDelegate
{
public override void LocationsUpdated(CLLocationManager manager, CLLocation[] locations)
{
//Handle your method here
}
}
You can test this with your simulator: Debug > Location > Freeway Drive. Store the locations to your sandbox folder in the event above. Force quit your app, after a few seconds open it, retrieve the file you stored. You will find the whole locations records.

Related

Please point me in the correct direction to understand how to run in the background in iOS

I'm working on a fitness app (using Xamarin) and would like it to continue running in the background if the user switches to another app or pushes the side button to blank out the screen. I have looked at multiple tutorials, include this one https://robgibbens.com/backgrounding-with-xamarin-forms/, which seemed very promising. However, when running my app and switching to another application or blanking the screen, my app simply suspends. Hers is the iOS specific code that starts the background task:
public class IOSPlayWorkoutTask
{
nint _taskID;
CancellationTokenSource _cts;
Workout _workout;
public async Task Start()
{
_cts = new CancellationTokenSource();
_taskID = UIApplication.SharedApplication.BeginBackgroundTask("IOSPlayWorkoutTask", OnExpiration);
try
{
WorkoutPlayer.Shared.Workout = _workout;
await WorkoutPlayer.Shared.PlayWorkout(_cts.Token);
}
catch (OperationCanceledException)
{
}
finally
{
if (_cts.IsCancellationRequested)
{
//Device.BeginInvokeOnMainThread(
// () => MessagingCenter.Send(new CancelPlayingWorkoutMessage(), CancelPlayingWorkoutMessage.MessageText));
}
}
UIApplication.SharedApplication.EndBackgroundTask(_taskID);
}
public void Pause()
{
WorkoutPlayer.Shared.RequestPause();
_cts.Cancel();
Device.BeginInvokeOnMainThread(
() => MessagingCenter.Send(new PausedWorkoutMessage(), PausedWorkoutMessage.MessageText));
}
public void Stop()
{
_cts.Cancel();
Device.BeginInvokeOnMainThread(
() => MessagingCenter.Send(new FinishedWorkoutMessage(), FinishedWorkoutMessage.MessageText));
}
void OnExpiration()
{
_cts.Cancel();
}
public IOSPlayWorkoutTask(Workout w)
{
_workout = w;
}
}
The iOS app delegate registers to receive messages, one of which starts the task above. I know I must be missing something very basic. Any help would be greatly appreciated. I know this has to be possible based on my experience using other fitness apps like Couch to 5k.
Thanks #sushihangover and #Paulw11 for your input. Looks like the secret is adding a handler to BeginBackgroundTask that calls EndBackgroundTask. Not sure I understand fully at this point, but apparently this give iOS the impression that you are a "good citizen", but continues processing your task. Here is my code that works:
public async Task Start()
{
_cts = new CancellationTokenSource();
//_taskID = UIApplication.SharedApplication.BeginBackgroundTask("IOSPlayWorkoutTask", OnExpiration);
_taskID = UIApplication.SharedApplication.BeginBackgroundTask(() =>
{
Console.WriteLine("Guess my time is up");
UIApplication.SharedApplication.EndBackgroundTask(_taskID);
});
try
{
WorkoutPlayer.Shared.Workout = _workout;
await WorkoutPlayer.Shared.PlayWorkout(_cts.Token);
//finished = true;
}
catch (OperationCanceledException)
{
Console.WriteLine("OperationCanceledException");
//finished = true;
}
finally
{
if (_cts.IsCancellationRequested)
{
//Device.BeginInvokeOnMainThread(
// () => MessagingCenter.Send(new CancelPlayingWorkoutMessage(), CancelPlayingWorkoutMessage.MessageText));
}
}
UIApplication.SharedApplication.EndBackgroundTask(_taskID);
}

Xamarin-IOS BTLE WroteCharacteristicValue not fired

I have the following code for my IOS implementation, the problem is that the WroteCharacteristicValue event is never fired. Is is being fired on the android side when I connect to the same module. Any ideas what to do?
public void StartUpdates ()
{
// TODO: should be bool RequestValue? compare iOS API for commonality
bool successful = false;
if(CanRead) {
Console.WriteLine ("** Characteristic.RequestValue, PropertyType = Read, requesting read");
_parentDevice.UpdatedCharacterteristicValue += UpdatedRead;
_parentDevice.ReadValue (_nativeCharacteristic);
successful = true;
}
if (CanUpdate) {
Console.WriteLine ("** Characteristic.RequestValue, PropertyType = Notify, requesting updates");
_parentDevice.UpdatedCharacterteristicValue += UpdatedNotify;
_parentDevice.WroteCharacteristicValue += Wrote; // -DP here??
_parentDevice.SetNotifyValue (true, _nativeCharacteristic);
successful = true;
}
Console.WriteLine ("** RequestValue, Succesful: " + successful.ToString());
}
void Wrote(object sender, CBCharacteristicEventArgs e) {
System.Diagnostics.Debug.WriteLine("Characteristic Write Complete!");
this.WriteComplete (this, new CharacteristicReadEventArgs () {
Characteristic = new Characteristic(e.Characteristic, _parentDevice)
});
}
The WroteCharacteristic will only fire if the characteristic writes with response.
You can check it with:
var prop = _nativeCharacteristic.Properties;
if(prop.HasFlag(CBCharacteristicProperties.Write))
{
// Event can be used
}
else if(prop.HasFlag(CBCharacteristicProperties.WriteWithoutResponse))
{
// Event will not fire if WriteWithoutResponse
}
Btw: we provide a plugin for BLE, so you don't have to care about platform sepcific stuff ;) http://smstuebe.de/2016/05/13/blev1.0/

xamarin forms permission to use location iOS

I'm using Xamarin.Forms and creating an iOS App who has a background service to get a location each 10 minutes.
The code is working, my problem is when I access the App configuration on an IPad.
It shows the permission for accesss the camera but not to access the current location.
I think that will be a problem when I submit the App for review.
For initialize the location:
this.locMgr = new CLLocationManager();
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
locMgr.RequestAlwaysAuthorization(); // works in background
//locMgr.RequestWhenInUseAuthorization (); // only in foreground
}
For getting the location:
if (CLLocationManager.LocationServicesEnabled)
{
if (CLLocationManager.Status==CLAuthorizationStatus.Authorized
|| CLLocationManager.Status==CLAuthorizationStatus.AuthorizedAlways)
{
//set the desired accuracy, in meters
LocMgr.DesiredAccuracy = 1;
LocMgr.LocationsUpdated += (object sender, CLLocationsUpdatedEventArgs e) =>
{
_currentLocation = (e.Locations[e.Locations.Length - 1]);
};
LocMgr.AuthorizationChanged += (object sender, CLAuthorizationChangedEventArgs e) =>
{
if (e.Status == CLAuthorizationStatus.Denied
|| e.Status == CLAuthorizationStatus.Restricted)
{
LocMgr.StopUpdatingLocation();
_currentLocation = null;
}
};
LocMgr.StartUpdatingLocation();
}
}
There are something that I forgot?
Did you add these keys to your Info.plist file?
<key>NSLocationAlwaysUsageDescription</key>
<string>Your message goes here</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your message goes here</string>

Monotouch : pause application unit dialog response

How can i pause application for prevention from running next method unit client does not selected dialog buttons?
For example i am showing location update dialog for accessing location service and i want to pause my application for dialog response
public CLLocation UpdateUserLocation()
{
CLLocation currentLocation = null;
CLLocationManager LocMgr = new CLLocationManager();
if (CLLocationManager.LocationServicesEnabled)
{
if (UIDevice.CurrentDevice.CheckSystemVersion (6, 0))
{
LocMgr.LocationsUpdated += (object sender, CLLocationsUpdatedEventArgs e) =>
{
currentLocation = e.Locations [e.Locations.Length - 1];
};
}
else
{
LocMgr.UpdatedLocation += (object sender, CLLocationUpdatedEventArgs e) =>
{
currentLocation = e.NewLocation;
};
}
LocMgr.StartUpdatingLocation ();
LocMgr.Failed += (object sender, NSErrorEventArgs e) =>
{
Console.WriteLine (e.Error);
};
}
else
{
currentLocation = null;
Console.WriteLine ("Location services not enabled, please enable this in your Settings");
}
if (currentLocation != null)
{
LocationDetector.Instance.UpdateCurrentArea (new MyLatLng (currentLocation.Coordinate.Latitude, currentLocation.Coordinate.Longitude));
}
return currentLocation;
}
If I am understanding your question correctly.
When you display a dialog box, you are wanting to stop execution of the current method from further executing until the user selects a dialog box response.
Once they have selected a response, you would then like to continue execution of the code in the same function, effectively achieving your 'pause' that you are after.
To achieve this in iOS you can use a TaskCompletionSource.
In the example below it shows a dialog box first, asking the user if they want some coffee and then waits for the user to respond.
Once the user responds, it then continues execution, within the same function, and displays a further message box that is dependent on the selection that the user made.
UIButton objButton1 = new UIButton (UIButtonType.RoundedRect);
objButton1.SetTitle ("Click Me", UIControlState.Normal);
objButton1.TouchUpInside += (async (o2, e2) => {
int intCoffeeDispenserResponse = await ShowCoffeeDispenserDialogBox();
//
switch (intCoffeeDispenserResponse)
{
case 0:
UIAlertView objUIAlertView1 = new UIAlertView();
objUIAlertView1.Title = "Coffee Dispenser";
objUIAlertView1.Message = "I hope you enjoy the coffee.";
objUIAlertView1.AddButton("OK");
objUIAlertView1.Show();
break;
case 1:
UIAlertView objUIAlertView2 = new UIAlertView();
objUIAlertView2.Title = "Coffee Dispenser";
objUIAlertView2.Message = "OK - Please come back later when you do.";
objUIAlertView2.AddButton("OK");
objUIAlertView2.Show();
break;
}
});
//
View = objButton1;
private Task<int> ShowCoffeeDispenserDialogBox()
{
TaskCompletionSource<int> objTaskCompletionSource1 = new TaskCompletionSource<int> ();
//
UIAlertView objUIAlertView1 = new UIAlertView();
objUIAlertView1.Title = "Coffee Dispenser";
objUIAlertView1.Message = "Do you want some coffee?";
objUIAlertView1.AddButton("Yes");
objUIAlertView1.AddButton("No");
//
objUIAlertView1.Clicked += ((o2, e2) => {
objTaskCompletionSource1.SetResult(e2.ButtonIndex);
});
//
objUIAlertView1.Show();
//
return objTaskCompletionSource1.Task;
}

Showing the location in Windows Phone 7 works in the emulator but not on the real phone

I have code that is used to show a device's location. It works just fine on the emulator and it takes me to the fake location at Microsoft. But it didn't work when I build it into the phone, it showed me the world map. Is this a known bug or I have done something wrong? Here is my code:
private GeoCoordinateWatcher loc = null;
private void button1_Click(object sender, RoutedEventArgs e)
{
if (loc == null)
{
loc = new GeoCoordinateWatcher(GeoPositionAccuracy.Default);
loc.StatusChanged += loc_StatusChanged;
}
if (loc.Status == GeoPositionStatus.Disabled)
{
loc.StatusChanged -= loc_StatusChanged;
MessageBox.Show("Location services must be enabled on your phone.");
return;
}
loc.Start();
}
void loc_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
{
if (e.Status == GeoPositionStatus.Ready)
{
Pushpin p = new Pushpin();
p.Template = this.Resources["pinMyLoc"] as ControlTemplate;
p.Location = loc.Position.Location;
mapControl.Items.Add(p);
map1.SetView(loc.Position.Location, 17.0);
loc.Stop();
}
}
}
Instead of using the StatusChanged event, you should use the GeoCoordinateWatcher.PositionChanged event, in from which you should use the GeoPositionChangedEventArgs.Position property, to reflect the changed location.
This is due to my location doesn't support by Bing Map. I couldn't use the Bing Map app installed in my phone neither. Hmm...

Resources