Service variables reset when calling activity closed in android - c#

I am having this issue for a couple of days now, i did a lot of searching in the Internet, but did not find something that works in my case.
I have a service :
namespace Demo_Service_Name
{
public static class MyGlobals
{
...
public static int DurSelection;
}
class Service_Connection : Java.Lang.Object, IServiceConnection
{...}
public class Demo_Service : Service
{
public void Setup_Service(int DSel)
{
MyGlobals.DurSelection = DSel;
}
public int Get_Duration_Selection()
{
return MyGlobals.DurSelection;
}
...
}
}
and my Activity goes like this:
using Demo_Service_Name
namespace Demo_Activity
{
public class Activity1 : Activity
{
protected override void OnCreate(Bundle bundle)
{
....
ThisService = new Intent(this, typeof(Demo_Service));
if (isMyServiceRunning())
{
demoServiceConnection = new Neural_Service_Connection(this);
isBound = BindService(ThisService, demoServiceConnection, Bind.none);
int servVar = binder.GetDemoService().Get_Duration_Selection();
}
}
protected void OnStartMyService()
{
....
StartService(ThisService);
isBound = BindService(ThisService, demoServiceConnection, Bind.none);
binder.GetDemoService().Setup_Service(A_Certain_Number);
}
}
}
So what I want to do is to start the service from the activity, bind to it, pass a value, close the activity, reopen the activity and read the variable back into the activity if the service is running.
All works like it should, except that when i close the activity, the value in the service variable is reset.
What do you think am I missing?
PS. I am sure the Service.OnDestroy has not been called

Related

Android Xamarin detect started from task manager

I am currently developing an android xamarin app (android 6 and above) and I have got a problem.
Our customer wants to secure the app by a pinpad. Whenever the app is started the user has to enter a four digit pin.
We have created an activity for the pinpad. This works pretty fine, but the problem is the following:
The pinpad just opens if the app was completely killed (e.g. by the task manager ) -> cold started.
How can I achive that the pinpad opens if the app was in the background and reopend by the task manager for example (user pressed home button and then wants to start app again) -> warm started.
I've tried to do this by OnResume(), OnStart(),. But unfortunately they trigger every time an another activity (e.g. open detail view of list item) is opened.
use IActivityLifecycleCallbacks to listen the status.
Application registration ActivityLifecycleCallbacks, such, when each activity in the app lifecycle occurs, the Application can be listening to. The number of public void onActivityStarted(activity activity) and public void onActivityStopped(activity activity) of an activity can be used to determine whether the app is in the foreground. Because when the app is in the foreground, an activity must have started onActivityStarted but not onActivityStopped, so the statistics of the number of activities opened in the app must be 1. When the app switches to the background, activityStartCount will be 0.
so write a Helper classes :
public class AppFrontBackHelper
{
public static OnAppStatusListener mOnAppStatusListener;
private LifecycleCallBack lifecycleCallBack;
public AppFrontBackHelper()
{
}
/**
* Register status listener, only used in Application
* #param application
* #param listener
*/
public void register(Application application, OnAppStatusListener listener)
{
mOnAppStatusListener = listener;
lifecycleCallBack = new LifecycleCallBack();
application.RegisterActivityLifecycleCallbacks(lifecycleCallBack);
}
public void unRegister(Application application) => application.UnregisterActivityLifecycleCallbacks(lifecycleCallBack);
public interface OnAppStatusListener
{
void onFront();
void onBack();
}
public class LifecycleCallBack : Java.Lang.Object, Application.IActivityLifecycleCallbacks
{
public int activityStartCount { get; private set; }
public void OnActivityCreated(Activity activity, Bundle savedInstanceState)
{
}
public void OnActivityDestroyed(Activity activity)
{
}
public void OnActivityPaused(Activity activity)
{
}
public void OnActivityResumed(Activity activity)
{
}
public void OnActivitySaveInstanceState(Activity activity, Bundle outState)
{
}
public void OnActivityStarted(Activity activity)
{
activityStartCount++;
//A value from 1 to 0 indicates cutting from the background to the foreground
if (activityStartCount == 1)
{
if (mOnAppStatusListener != null)
{
mOnAppStatusListener.onFront();
}
}
}
public void OnActivityStopped(Activity activity)
{
activityStartCount--;
//A value from 1 to 0 indicates cutting from the foreground to the background
if (activityStartCount == 0)
{
//从前台切到后台
if (mOnAppStatusListener != null)
{
mOnAppStatusListener.onBack();
}
}
}
}
}
then custom an Application and regist the listener :
[Application]
public class MyApplication : Application,AppFrontBackHelper.OnAppStatusListener
{
protected MyApplication(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
public override void OnCreate()
{
base.OnCreate();
AppFrontBackHelper appFrontBackHelper = new AppFrontBackHelper();
appFrontBackHelper.register(this, this);
}
public void onBack()
{
Toast.MakeText(this, "from front to back", ToastLength.Short).Show();
}
public void onFront()
{
Toast.MakeText(this, "from back to front", ToastLength.Short).Show();
}
}
you could do something in the onFront() callback.

load different class of same type based on name

This is a really simple structure question, but i don't know what is the best way to do this in C#.
I need a "base" class or something to allow me load diferent "levels" in the "level" object, and changing the name I can swith the class what I want to use.
Now i'm using a abstract class and instance of the childrens in this way:
using UnityEngine;
using System;
public class Level : MonoBehaviour {
public levelClass level;
public void initLevel(string className) {
Type t = Type.GetType(className);
level = (levelClass)Activator.CreateInstance(t, new object[] { });
level.Start();
}
void Start () {
Debug.Log("Levels: Start");
initLevel("worldTwo");
}
void Update () {
level.Update();
}
}
public abstract class levelClass {
public abstract void Start();
public abstract void Update();
}
public class worldOne : levelClass {
public override void Start() {
Debug.Log("worldOne: Start");
}
public override void Update() {
Debug.Log("worldOne: Update");
}
}
public class worldTwo : levelClass
{
public override void Start()
{
Debug.Log("worldTwo: Start!");
}
public override void Update()
{
Debug.Log("worldTwo: Update!");
}
}
But I think exists better ways to do, like store all classes in a list and call it or something similar. The part i don't like nothing is have some classes public in this file (I know i can split in other files, i'm trying to think only in a way to do this better)

BroadcastReceiver does not receive broadcast

My BroadcastReceiver does not receive anything. Most likely its my setup that is wrong, because I was not able to find any good examples on this. I need my receiver to receive something in my MainActivity, and change a View. I have almost the same code in an Android project, and here it is working, however BroadcastReceivers seems to be implemented a tiny bit differently in Xamarin (in Android, I can make a new BroadcastReceiver almost like an object, but in Xamarin, or C#, it seems I must make my own class and thus do not have the same possibilities to directly reference the views). If I get this to work, I will post a full working example for everyone too.
Here is how I have tried to set it up:
[Activity(Label = "GetLocation.Droid", MainLauncher = true, Icon = "#drawable/icon")]
public class MainActivity : Activity
{
Button button;
protected override void OnCreate(Bundle bundle)
{
// ... various OnCreate() code
LocationBroadcastReciever lbr = new LocationBroadcastReciever();
RegisterReceiver(lbr, new IntentFilter("test"));
}
public void SetButtonText(string text)
{
button.Text = text;
}
}
[BroadcastReceiver]
public class LocationBroadcastReciever : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
/* My program never get this far, so I have not been able
to confirm if the bellow code works or not (its from
another example I saw). */
//EDIT: It does NOT work. See my answer for a working example
string text = intent.GetStringExtra("title");
((MainActivity)context).SetButtonText(text);
InvokeAbortBroadcast();
}
}
And in my IntentService I have this method that actually runs, but never arrives at my receiver.
private void SendBroadcast(double lat, double lng, string activity)
{
Intent intent = new Intent("test");
intent.PutExtra("title", "Updated");
LocalBroadcastManager.GetInstance(this).SendBroadcast(intent);
}
This is pretty much the same code as I have in my working Android (only tweaked the BroadcastReceiver and minor adjustments to make it compile).
Can anyone see whats wrong??
EDIT
Finally got this whole thing to work. You can see my answer for a full, clean example.
Local
You register receiver as global, but send intents via LocalBroadcastManager. If you want to use this manager you should register your receiver like this:
LocalBroadcastManager.GetInstance(this).RegisterReceiver(lbr, filter);
You can find more about LocalBroadcastManager here.
Global
Or if you want to use global broadcasts, you should create intent by type:
var intent = new Intent(this, typeof(LocationBroadcastReciever));
and send it via android Context (in your service):
this.SendBroadcast(intent);
Also you can use intent with action, but it requires IntentFilter attribute on your receiver:
[IntentFilter(new []{ "test" })]
[BroadcastReceiver]
public class LocationBroadcastReciever : BroadcastReceiver { ... }
For the sake of future search results, here is a clean example of my code with a working BroadcastReceiver
// ** MainActivity
namespace GetLocation.Droid
{
[Activity(Label = "GetLocation.Droid", MainLauncher = true, Icon = "#drawable/icon")]
public class MainActivity : Activity
{
//I initialize my view(s) here to access them from outside of OnCreate().
Button button;
//I found this in an Android BroadcastReceiver example of how to access the MainActivity from the BroadcastReceiver.
private static MainActivity ins;
public static MainActivity getInstace()
{
return ins;
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
button = FindViewById<Button>(Resource.Id.myButton);
ins = this;
button.Click += delegate
{
Intent intent = new Intent(this, typeof(MyIntentService));
StartService(intent);
};
LocationBroadcastReciever lbr = new LocationBroadcastReciever();
LocalBroadcastManager.GetInstance(this).RegisterReceiver(lbr, new IntentFilter("test"));
}
public void SetButtonText(string text)
{
button.Text = text;
}
}
[BroadcastReceiver]
[IntentFilter(new[] { "test" })]
public class LocationBroadcastReciever : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
string text = intent.GetStringExtra("title");
MainActivity.getInstace().SetButtonText(text);
}
}
}
And in my IntentService
namespace GetLocation.Droid
{
[Service]
[IntentFilter(new String[] { "com.mytos.MyIntentService" })]
public class MyIntentService : IntentService
{
protected override void OnHandleIntent(Intent intent)
{
SendBroadcast("My message");
}
private void SendBroadcast(string message)
{
//Here you can of course send whatever variable you want. Mine is a string
Intent intent = new Intent("test");
intent.PutExtra("title", message);
LocalBroadcastManager.GetInstance(this).SendBroadcast(intent);
}
}
}
A couple of things:
First, you need the [IntentFilter] on the receiver. So it should look like....
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new [] { "test" })]
public class LocationBroadcastReciever : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
/* My program never get this far, so I have not been able
to confirm if the bellow code works or not (its from
another example I saw). */
string text = intent.GetStringExtra("title");
((MainActivity)context).SetButtonText(text);
InvokeAbortBroadcast();
}
}
That should get you past your issue.
Second, you should register and unregister the reciever. So you should register in the OnResume and unregister in OnPause.
[Activity(Label = "GetLocation.Droid", MainLauncher = true, Icon = "#drawable/icon")]
public class MainActivity : Activity
{
LocationBroadcastReciever _lbr;
Button button;
protected override void OnCreate(Bundle bundle)
{
// ... various OnCreate() code
}
protected override void OnResume()
{
base.OnResume();
_lbr = new LocationBroadcastReciever();
RegisterReceiver(lbr, new IntentFilter("test"));
}
protected override void OnPause()
{
UnregisterReceiver(_lbr);
base.OnPause();
}
public void SetButtonText(string text)
{
button.Text = text;
}
}
Note: these changes are what I saw different between your code and my code for a working broadcast receiver. Whether my changes are necessary or not, I'm not entirely sure.

Raise Error, dispose object and re-create it

Let's assume we have the following two classes, How can we listen for Errors and if any error occurred, recreate the singleton? I have put together the following code, but would like to know if there is a pattern for safely raise error, dispose object and recreate it automatically?
`
static void Main(string[] args)
{
MyFirstClass.Instance.SayHello();
}
}
class MySecondClass
{
public int ID { get; set; }
public void SayHelloFromSecondClass()
{
Console.WriteLine("Say Hello From Second Class");
}
public MySecondClass(int id)
{
ID = id;
}
}
public sealed class MyFirstClass
{
private static readonly MyFirstClass instance = new MyFirstClass();
private static MySecondClass msc;
public event EventHandler ErrorOccuredEvent;
private MyFirstClass() { }
public static MyFirstClass Instance
{
get
{
msc = new MySecondClass(id: 1);
return instance;
}
}
public void SayHello()
{
Console.WriteLine("Hello World...");
}
static void ErrorOccured(object sender, EventArgs e)
{
Console.WriteLine("Oops");
msc = null;
Thread.Sleep(5000);
GC.Collect();
msc = new MySecondClass(id: 2);
}
}
`
If I understand well, MyFirstClass (which is a singleton) is a kind of wrapper around MySecondClass that turns MySecondClass into a singleton as well.
Let's call MyFirstClass: Wrapper
Let's call MySecondClass: Service
If the clients always consume the Service through the single instance of Wrapper, then re-creating a Wrapper will not help, because the clients might keep a reference to Wapper. Re-creating Service can help if the clients don't see it and cannot keep a reference to it. Therefore they must consume the service indirectly.
It's easiest to achieve this through an interface:
public interface IHelloService
{
void SayHello();
}
public class HelloService : IHelloService
{
public void SayHello()
{
Console.WriteLine("Hello");
}
}
public class HelloServiceWrapper : IHelloService
{
public static readonly IHelloService Instance = new HelloServiceWrapper();
private HelloServiceWrapper () {}
private IHelloService _service;
public void SayHello()
{
EnsureServiceAvailable();
_service.SayHello();
}
private void EnsureServiceAvailable()
{
if(_service == null) {
_service = new HelloService();
}
}
private void HandleError()
{
_service = null;
}
}
But if the error happens when the client is using the service ...
HelloServiceWrapper.Instace.SayHello();
... this call might fail.
You would have to re-create the service instantly in order to make succeed the client's call (assuming that re-creating the service will solve the problem and that the error will not occur again immediately):
public void SayHello()
{
try {
_service.SayHello();
} catch {
_service = new HelloService();
_service.SayHello();
}
}
Note: Disposing the service invalidates the object and makes any reference a client has to it invalid. But re-creating a new one does not give the client a new reference! You would need to have a reference to the clients reference in order to be able to give the client a new instance.

How to subscribe to an event with certain arguments c#?

I have an enumeration prior.
Each of my scripts has a property priority of prior type. (Every script has its own class)
I have a data provider, which can send events every frame.
I want a script to subscribe only to an event which has arguments with priority equal to the script's one.
For example, a script with moderate priority should receive only events with moderate parameter of event arguments
prior has too many members to create a special event argument class for each.
Unfortunately:
a)I know only how to subscribe to a certain event type.
b)I can't make a generic class for event arguments, because elements of enum are not types
How can I do it?
The project currently looks this way:
public class TDefault:MonoBehaviour,IDefault
{
public enum prior
{
none,
...,
terminal
};
prior priority;
public virtual void apply()//For override by scripts
{
}
void Start()
{
//There should be adding a method which calls apply() when event_manager
//sends Event with a certain priority
}
public TDefault ()
{
if(essential==null)
essential=new TEssential();
}
}
public class TApplyEventParam : EventArgs
{
public TDefault.prior priority;
public TApplyEventParam(TDefault.prior _priority)
{
priority=_priority;
}
}
public class event_manager : TDefault
{
//This has fixed type
event EventHandler<TApplyEventParam> handler=new EventHandler<TApplyEventParam>();
void Update ()
{
foreach (prior p in (prior[]) Enum.GetValues(typeof(prior)))
{
handler(this,new TApplyEventParam(p));
}
}
}
The problem you're dealing with, if I understood it correctly, is that you would like to have your event subscription conditionally called depending on the event payload (the priority value inside the TApplyEventParam). That is something that you cannot do which results in you having to filter out the unwanted events inside your event handler like proposed by #Henk-Holterman
Another approach could be to skip the usage of events and maintain your own list of subscribers inside the data provider.
Based on the terminology used by you in your question (not the code example) you could do something like this:
using System;
using System.Collections.Generic;
namespace Example
{
public enum Prior
{
None,
Moderate,
Terminal
};
public abstract class ScriptBase
{
public abstract Prior Prior { get; }
public abstract void Apply();
public void Start(DataProvider dataProvider)
{
dataProvider.Subscribe(Prior, Apply);
}
public void Stop(DataProvider dataProvider)
{
dataProvider.Unsubscribe(Prior, Apply);
}
}
public class ScriptHandlingModerateEvents : ScriptBase
{
public override Prior Prior
{
get { return Example.Prior.Moderate; }
}
public override void Apply()
{
Console.WriteLine("Handling moderate event by " + GetType().Name);
}
}
public class ScriptHandlingTerminalEvents : ScriptBase
{
public override Prior Prior
{
get { return Example.Prior.Terminal; }
}
public override void Apply()
{
Console.WriteLine("Handling terminal event by " + GetType().Name);
}
}
public class DataProvider
{
private readonly Dictionary<Prior, List<Action>> _subscribersByPrior;
public DataProvider()
{
_subscribersByPrior = new Dictionary<Prior, List<Action>>();
foreach (Prior prior in (Prior[])Enum.GetValues(typeof(Prior)))
{
_subscribersByPrior.Add(prior, new List<Action>());
}
}
public void Subscribe(Prior prior, Action action)
{
_subscribersByPrior[prior].Add(action);
}
public void Unsubscribe(Prior prior, Action action)
{
_subscribersByPrior[prior].Remove(action);
}
public void DoSomethingThatTriggersPriorEvents(int someValue)
{
Prior prior = someValue % 2 == 0 ? Prior.Moderate : Prior.Terminal;
foreach (var subscriber in _subscribersByPrior[prior])
{
subscriber();
}
}
}
public static class Program
{
public static void Main()
{
DataProvider dataProvider = new DataProvider();
var scriptHandlingModerateEvents = new ScriptHandlingModerateEvents();
scriptHandlingModerateEvents.Start(dataProvider);
var scriptHandlingTerminalEvents = new ScriptHandlingTerminalEvents();
scriptHandlingTerminalEvents.Start(dataProvider);
for (int i = 0; i < 10; i++)
{
dataProvider.DoSomethingThatTriggersPriorEvents(i);
}
scriptHandlingTerminalEvents.Stop(dataProvider);
scriptHandlingModerateEvents.Stop(dataProvider);
Console.WriteLine();
}
}
}
this way the DataProvider is not aware of scripts, but if that is not an issue, you could maintain a list of ScriptBase instances and check the Prior property inside the
DoSomethingThatTriggersPriorEvents like this:
public class DataProvider2
{
private readonly List<ScriptBase> _scripts = new List<ScriptBase>();
public void Subscribe(ScriptBase script)
{
_scripts.Add(script);
}
public void Unsubscribe(ScriptBase script)
{
_scripts.Remove(script);
}
public void DoSomethingThatTriggersPriorEvents(int someValue)
{
Prior prior = someValue % 2 == 0 ? Prior.Moderate : Prior.Terminal;
foreach (var script in _scripts)
{
if (script.Prior == prior)
{
script.Apply();
}
}
}
}

Categories