Hi there I am using modified AltBeacon library sample: https://github.com/acaliaro/AltBeaconLibrarySample
I am starting the service in MainPage instead of App Main (Because I need to login first where I get Beacons from the server, save them to local storage (for quick retrival later))
My App.cs only has resume methos
public partial class App : Application
{
bool _closeTimer = false;
public static ICrossTimer timer; //to gain acsess to timer class
public static CAPI api; //To get acsess to our API
public App()
{
InitializeComponent();
DependencyService.Register<MockDataStore>();
App.api = new CAPI(); //To get acsess to our API
//if API KEY is invalid or doesn't exists yet, prompt user to login
string API_LOGIN = Preferences.Get("API_KEY_LOGIN", null);
string API_SECRET = Preferences.Get("API_KEY_SECRET", null);
DateTime APIKeyDate = Preferences.Get("API_KEY_STATUS", DateTime.Parse("1/1/1970"));
string API_URL = Preferences.Get("API_URL", null);
if (DateTime.Now > APIKeyDate) //Key is expired if its date is smaller than todays date
MainPage = new Views.LoginPage();
else
{
App.api.LoginWithAPIKey(Constants.TENANTID, API_LOGIN, API_SECRET, APIKeyDate, API_URL);
App.IsUserLoggedIn = true;
MainPage = new MainPage();
}
}
// Use a service for providing this information
public static bool IsUserLoggedIn { get; set; }
protected override void OnStart() // Handle when your app starts
{
DependencyService.Get<IAltBeaconService>().SetBackgroundMode(false);
startTimer();
}
protected override void OnSleep() // Handle when your app sleeps
{
DependencyService.Get<IAltBeaconService>().SetBackgroundMode(true); //Why is this usefull with this I need to wait even longer before OnEntered Region is called and the service gets killed anyway :(
closeTimer();
}
protected override void OnResume() // Handle when your app resumes
{
DependencyService.Get<IAltBeaconService>().SetBackgroundMode(false);
startTimer();
}
void startTimer()
{
_closeTimer = false;
Device.StartTimer(TimeSpan.FromSeconds(10), () =>
{
if (_closeTimer)
{
System.Diagnostics.Debug.WriteLine("StartTimer: stop repeating");
return false;
}
Xamarin.Forms.MessagingCenter.Send<App>((App)Xamarin.Forms.Application.Current, "CleanBeacons");
System.Diagnostics.Debug.WriteLine("StartTimer: end with " + (!_closeTimer ? "repeating" : "stop repeating"));
return !_closeTimer;
});
}
private void closeTimer()
{
_closeTimer = true;
}
}
[assembly: Xamarin.Forms.Dependency(typeof(AltBeaconService))]
namespace Calimero.Droid.Services
{
public class AltBeaconService : Java.Lang.Object, IAltBeaconService
{
bool BeaconEventCalled = false;
private readonly Calimero.Droid.Notifiers.MonitorNotifier _monitorNotifier;
private readonly RangeNotifier _rangeNotifier;
private BeaconManager _beaconManager;
Org.Altbeacon.Beacon.Region _tagRegion;
Org.Altbeacon.Beacon.Region _emptyRegion;
public AltBeaconService()
{
_monitorNotifier = new Calimero.Droid.Notifiers.MonitorNotifier();
_rangeNotifier = new RangeNotifier();
}
public BeaconManager BeaconManagerImpl
{
get
{
if (_beaconManager == null)
_beaconManager = InitializeBeaconManager();
return _beaconManager;
}
}
public void InitializeService()
{
if (_beaconManager == null)
_beaconManager = InitializeBeaconManager();
}
private BeaconManager InitializeBeaconManager()
{
// Enable the BeaconManager
BeaconManager bm = BeaconManager.GetInstanceForApplication(Plugin.CurrentActivity.CrossCurrentActivity.Current.Activity);
var iBeaconParser = new BeaconParser();
// Estimote > 2013
iBeaconParser.SetBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24");
bm.BeaconParsers.Add(iBeaconParser);
_monitorNotifier.EnterRegionComplete += EnteredRegion;
_monitorNotifier.ExitRegionComplete += ExitedRegion;
_monitorNotifier.DetermineStateForRegionComplete += DeterminedStateForRegionComplete;
_rangeNotifier.DidRangeBeaconsInRegionComplete += RangingBeaconsInRegion;
//Get Region from storage
string BEACON_REGION = Preferences.Get("BEACON_REGION", null);
if(BEACON_REGION != null)
_tagRegion = new Org.Altbeacon.Beacon.Region("MyRegion", Identifier.Parse(BEACON_REGION), null, null);
else
_tagRegion = new Org.Altbeacon.Beacon.Region("MyRegion", Identifier.Parse("F7826DB4-2FG1-4E98-8024-BC0871E6693E"), null, null);
_emptyRegion = new Org.Altbeacon.Beacon.Region("myEmptyBeaconId", null, null, null);
bm.BackgroundMode = false;
bm.Bind((IBeaconConsumer)Plugin.CurrentActivity.CrossCurrentActivity.Current.Activity);
return bm;
}
public void StartMonitoring()
{
BeaconManagerImpl.ForegroundBetweenScanPeriod = 5000;
BeaconManagerImpl.BackgroundBetweenScanPeriod = 5000;
BeaconManagerImpl.AddMonitorNotifier(_monitorNotifier);
BeaconManagerImpl.StartMonitoringBeaconsInRegion(_tagRegion);
BeaconManagerImpl.StartMonitoringBeaconsInRegion(_emptyRegion);
}
public void StartRanging()
{
BeaconManagerImpl.ForegroundBetweenScanPeriod = 100;
BeaconManagerImpl.BackgroundScanPeriod = 500;
BeaconManagerImpl.BackgroundBetweenScanPeriod = 30000;
BeaconManagerImpl.ForegroundScanPeriod = 200;
BeaconManagerImpl.AddRangeNotifier(_rangeNotifier);
try
{
BeaconManagerImpl.StartRangingBeaconsInRegion(_tagRegion);
BeaconManagerImpl.StartRangingBeaconsInRegion(_emptyRegion);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("StartRangingException: " + ex.Message);
}
}
public void StopRanging()
{
if (_beaconManager != null)
{
try
{
BeaconManagerImpl.StopRangingBeaconsInRegion(_tagRegion);
BeaconManagerImpl.StopRangingBeaconsInRegion(_emptyRegion);
BeaconManagerImpl.RemoveRangeNotifier(_rangeNotifier);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("StopRangingException: " + ex.Message);
}
}
}
private void DeterminedStateForRegionComplete(object sender, MonitorEventArgs e)
{
Console.WriteLine("DeterminedStateForRegionComplete");
}
private void ExitedRegion(object sender, MonitorEventArgs e)
{
string region = "???";
if (e.Region != null)
{
if (e.Region.Id1 == null)
region = "null region";
else
region = e.Region.Id1.ToString().ToUpper();
}
CrossLocalNotifications.Current.Show("Beacon", "You Exited region");
Calimero.Util.Malca.startBreak();
BeaconEventCalled = false;
}
private void EnteredRegion(object sender, MonitorEventArgs e)
{
string region = "???";
if (e.Region != null)
{
if (e.Region.Id1 == null)
region = "null region";
else
region = e.Region.Id1.ToString().ToUpper();
}
CrossLocalNotifications.Current.Show("Beacon", "You Just Entered Region");
Calimero.Util.Malca.stopBreak();
BeaconEventCalled = true;
}
List<SharedBeacon> _sharedBeacons = new List<SharedBeacon>();
object _lock = new object();
void RangingBeaconsInRegion(object sender, RangeEventArgs e)
{
_sharedBeacons = new List<SharedBeacon>();
lock (_lock)
{
// Get all beacons and create the SharedBeacon
foreach (Beacon beacon in e.Beacons)
{
System.Diagnostics.Debug.WriteLine(string.Format("NAME {0} - IP {1} - {2}dB", beacon.BluetoothName, beacon.BluetoothAddress, beacon.Rssi));
_sharedBeacons.Add(new SharedBeacon(beacon.BluetoothName, beacon.BluetoothAddress, beacon.Id1.ToString(), beacon.Id2.ToString(), beacon.Id3.ToString(), beacon.Distance, beacon.Rssi));
};
if (!BeaconEventCalled) //if EnteredRegion is not invoked, invoke backup method
BackupBeaconAlert(_sharedBeacons);
}
}
//Backup way incase OnEntered was not called
/*
* if Beacon in local list is already in globalList and time between last addition is > 3 seconds
* Beacon is not in range anymore so remove it from GlobalList and Call OnExited
* if time between last addition is < 3 seconds, then its new beacon so call OnEntered
*/
//Dictionary<DateTime, SharedBeacon> GlobalList = new Dictionary<DateTime, SharedBeacon>();
List<SharedBeacon> GlobalList = new List<SharedBeacon>();
void BackupBeaconAlert(List<SharedBeacon> localList)
{
// Go through localList first and compare
foreach (SharedBeacon b in localList)
{
if (GlobalList.Count == 0) //GlobalList is empty so there are not Beacons in the area
{
GlobalList.Add(b);
}
else
{
for (int i = 0; i < GlobalList.Count; i++)
{
DateTime storedTime = GlobalList[i].LastReceivedDateTime;
DateTime CurrentTime = DateTime.Now;
if ((CurrentTime - storedTime).TotalSeconds >= 3)
{
GlobalList.Remove(b);
BExitedRegion();
}
else
BEnteredRegion();
}
}
}
}
private void BExitedRegion() //Backup method when phone left beacon region
{
CrossLocalNotifications.Current.Show("Beacon", "You Exited region");
Calimero.Util.Malca.startBreak();
BeaconEventCalled = false;
}
private void BEnteredRegion() //Backup method when phone is in Beacon Region
{
CrossLocalNotifications.Current.Show("Beacon", "You Just Entered Region");
BeaconEventCalled = true;
}
public void SetBackgroundMode(bool isBackground)
{
if (_beaconManager != null)
BeaconManagerImpl.BackgroundMode = isBackground;
}
public void OnDestroy()
{
if (_beaconManager != null && BeaconManagerImpl.IsBound((IBeaconConsumer)Plugin.CurrentActivity.CrossCurrentActivity.Current.Activity))
BeaconManagerImpl.Unbind((IBeaconConsumer)Plugin.CurrentActivity.CrossCurrentActivity.Current.Activity);
}
}
}
now this service has 2 problems, which I have no idea how to fix, region detection doesn't work reliably, sometimes OnEntered region method is never called or it takes like 5 seconds for it to get called but I know that I am in region, because beacon is near me
so to mitigate this I implemented BackupBeaconAlert which speeds things up a bit but its still not realiable
or sometimes OnExited region method is called for no reason even if I am in the region, one time OnEntered method was called even when I had bluetooth turned off
and the second problem is that if you kill the app or close it (after a while when you close it) the service gets killed and beacons are not detecting anymore, as I am on Android 10 I would probably need to reimplement this beacon searching thing in Foreground service but I don't know how to convert this into Foreground service so it never gets killed?
Thanks for Anwsering and Best Regards