Xamarin.Forms Background Service prevent to start when app is running - c#

I managed to implement my Background Service which works really good. The Service connects to my WebSocket and keeps the connection alive after the App is closed.
I'm starting the Background Service when the App closes so that I can reconnect to the Backend, but I want to be able to close the Service so the App can now take care of the Connection.
I've tried Flags, Events, and other conditions to Stop the Service when the App starts but the Service Starts no matter what.
Is it possible to stop the Service when the App runs, so that the App can handle the Connection?
MainActivity.cs
[Activity(Label = "App",
Icon = "#drawable/ic_launcher",
Theme = "#style/splashscreen",
MainLauncher = true,
ScreenOrientation = ScreenOrientation.Portrait)]
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
SetTheme(Resource.Style.MainTheme);
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
Forms.Init(this, bundle);
LoadApplication(new App());
StartBackgroundService();
IsAppRunning = true;
}
private void StartBackgroundService() {
StopService = false;
RealtimeService.StopServiceEventHandler += StopServiceEventHandler;
StartService(new Intent(Forms.Context, typeof(BackgroundRealtimeService)));
}
private void StopServiceEventHandler(object sender, object o) {
if (StopService) {
StopService(new Intent(Forms.Context, typeof(BackgroundRealtimeService)));
}
}
protected override void OnDestroy()
{
IsAppRunning = false;
base.OnDestroy();
}
}
}
BackgroundRealtimeService.cs
[Service]
public class BackgroundRealtimeService : Service
{
public async void Initialize() {
// Service Code
}
public override void OnCreate() {
base.OnCreate();
}
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) {
new Task(Initialize).Start();
return StartCommandResult.Sticky;
}
public override IBinder OnBind(Intent intent)
{
return null;
}
}
}

Related

Xamarin.Forms Firebasepushnotification plugin OnNotificationOpened event causes app to restart for android

I'm trying to handle a notifications opening on android that I've sent through Google's FCM on the SharedProject level by using "Plugin.FirebasePushNotification" plugin and subscribing to "OnNotificationOpened" event.
When I try to open the notification while the app is in the background I'm able to hit a breakpoint inside "OnNotificationOpened" event but after the work inside the event ends the App const. is hit and the application restarts.
I want the application to continue from its last state and hit "OnResume" method that is located inside the App class.
I've done some research about it online and almost all of them were about how splash activity might cause this issue so I've disabled my splash activity by turning the whole "SplashActivity.cs" file into a comment except the namespace and usages and implemented the wanted features of splashactivity(style) inside the "MainActivity" class after that I made sure my "MainActivity" class was the only activity that had it's "MainLauncher" attribute set to true but the problem continues.
Here are the relevant classes:
FirebaseReg.cs
public class FirebaseReg
{
public static void FirebaseInit()
{
CrossFirebasePushNotification.Current.OnTokenRefresh += (s, p) =>
{
System.Diagnostics.Debug.WriteLine($"TOKEN : {p.Token}");
CrossFirebasePushNotification.Current.Subscribe("defaultTopic");
};
CrossFirebasePushNotification.Current.OnNotificationOpened += (s, p) =>
{
try
{
//Breakpoint hits here and I'm able to finish all my work without any exceptions
}
catch (Exception ex)
{
}
};
CrossFirebasePushNotification.Current.OnNotificationAction += (s, p) =>
{
};
CrossFirebasePushNotification.Current.OnNotificationReceived += Current_OnNotificationReceived;
}
private static void Current_OnNotificationReceived(object source, FirebasePushNotificationDataEventArgs e)
{
//DO WORK
}
}
App.xaml.cs
public partial class App : Application
{
public static Uri ServerUri = new Uri(" ");
public static bool IsInForeground { get; set; } = false;
public App()
{
try
{
InitializeComponent(); //After the work in OnNotificationOpened ends breakpoint goes here
Application.Current.UserAppTheme = OSAppTheme.Unspecified;
Application.Current.MainPage = new LoginPage();
}
catch (Exception ex)
{
}
}
protected override void OnStart()
{
IsInForeground = true;
}
protected override void OnSleep()
{
IsInForeground = false;
}
protected override void OnResume()
{
IsInForeground = true;
}
}
MainActivity.cs
[Activity(Label = "TestProject1", Theme = "#style/MyTheme.Splash", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize )]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.Window.RequestFeature(WindowFeatures.ActionBar);
// Name of the MainActivity theme you had there before.
// Or you can use global::Android.Resource.Style.ThemeHoloLight
base.SetTheme(Resource.Style.MainTheme);
base.OnCreate(savedInstanceState);
Rg.Plugins.Popup.Popup.Init(this);
UserDialogs.Init(this);
Xamarin.FormsGoogleMaps.Init(this, savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
FirebasePushNotificationManager.ProcessIntent(this ,Intent);
LoadApplication(new App());
}
protected override void OnNewIntent(Intent intent)
{
FirebasePushNotificationManager.ProcessIntent(this, intent);
base.OnNewIntent(intent);
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
MainApplication.cs
[Application]
public class MainApplication : Application, Application.IActivityLifecycleCallbacks
{
public MainApplication(IntPtr handle, JniHandleOwnership transer) : base(handle, transer)
{
}
public override void OnCreate()
{
base.OnCreate();
RegisterActivityLifecycleCallbacks(this);
if (Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
FirebasePushNotificationManager.DefaultNotificationChannelId = "DefaultChannel";
FirebasePushNotificationManager.DefaultNotificationChannelName = "defaultTopic";
}
#if DEBUG
FirebasePushNotificationManager.Initialize(this, true);
FirebaseRegister.FirebaseInit();
#else
FirebasePushNotificationManager.Initialize(this, false);
FirebaseRegister.FirebaseInit();
#endif
}
public override void OnTerminate()
{
base.OnTerminate();
UnregisterActivityLifecycleCallbacks(this);
}
public void OnActivityCreated(Activity activity, Bundle savedInstanceState)
{
CrossCurrentActivity.Current.Activity = activity;
}
public void OnActivityDestroyed(Activity activity)
{
}
public void OnActivityPaused(Activity activity)
{
}
public void OnActivityResumed(Activity activity)
{
CrossCurrentActivity.Current.Activity = activity;
}
public void OnActivitySaveInstanceState(Activity activity, Bundle outState)
{
}
public void OnActivityStarted(Activity activity)
{
CrossCurrentActivity.Current.Activity = activity;
}
public void OnActivityStopped(Activity activity)
{
}
}
From the Android docs, it states this:
If it has declared its launch mode to be "multiple" (the default) and
you have not set FLAG_ACTIVITY_SINGLE_TOP in the same intent, then it
will be finished and re-created; for all other launch modes or if
FLAG_ACTIVITY_SINGLE_TOP is set then this Intent will be delivered to
the current instance's onNewIntent().
That's why your app is restarting.
The launchMode attribute of the activity affects how the activity is launched.
singleTop, singleTask, or singleInstance should be used to prevent the notification intent from creating a new activity instance.
The flag FLAG_ACTIVITY_NEW_TASK doesn't influence a new activity being created, but makes the launched activity the root of a new task.
For more, check:
https://developer.android.com/guide/topics/manifest/activity-element.html#lmode
https://developer.android.com/guide/components/activities/tasks-and-back-stack

How to call same service instance from multiple activities in Android

I've just started looking into Xamarin and just can not to wrap around my head how to make multiple Activities have a reference same instance of service.
I am starting KeyPressedReceiver from MainActivity and start listening for power button being pressed.
When three click are being made, I am calling service method InitCancelActivity, which starts playing mp3 file and opens CancelActivity.
In CancelActivity there is a text field and a button. And when user press this button, I want the value from text field to be passes to the GeneralService method KillAlert.
The question is how to reference instance of GeneralService (which is already created) from CancelActivity, so I could call KillAlert?
And this part
if (_service == null)
_service = new GeneralService();
looks absolutely wrong. Should I instantiate it in MainActivity and pass to a KeyPressedReceiver constructor?
[Activity(Label = "TTTT", MainLauncher = true, Icon = "#drawable/icon")]
public class MainActivity : Activity
{
KeyPressedReceiver receiver;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
receiver = new KeyPressedReceiver();
RegisterReceiver(receiver, new IntentFilter(Intent.ActionScreenOn));
}
}
[BroadcastReceiver(Enabled = true)]
public class KeyPressedReceiver : BroadcastReceiver
{
private GeneralService _service;
private int _clicks = 0;
public override void OnReceive(Context context, Intent intent)
{
if (_service == null)
_service = new GeneralService();
_clicks++;
if (_clicks > 5)
{
_service.InitCancelActivity();
}
}
}
[Service(Name = "com.ff.GeneralService")]
public class GeneralService : Service {
private readonly Android.Media.MediaPlayer _player;
public GeneralService()
{
_player = new Android.Media.MediaPlayer();
}
public void RaiseAlert()
{
// start playing .mp3 file
}
public void KillAlert(string pass)
{
// stop playing .mp3 file
}
public void InitCancelActivity()
{
this.RaiseAlert();
var i = new Intent(this, typeof(CancelActivity));
i.SetFlags(ActivityFlags.NewTask);
this.StartActivity(i);
}
}
[Activity(Label = "CancelActivity")]
public class CancelActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.cancel);
this.FindViewById(Resource.Id.cancelButtonYes).Click += delegate
{
var password = this.FindViewById(Resource.Id.cancelPassword);
// call KillAlert method from GeneralServic
};
}
}
Create static GeneralService instance and use in Cancel Activity.
for example
[Service(Name = "com.ff.GeneralService")]
public class GeneralService : Service {
private readonly Android.Media.MediaPlayer _player;
public static generalService;
public GeneralService()
{
_player = new Android.Media.MediaPlayer();
generalService=this
}
public void RaiseAlert()
{
// start playing .mp3 file
}
public void KillAlert(string pass)
{
// stop playing .mp3 file
}
public void InitCancelActivity()
{
this.RaiseAlert();
var i = new Intent(this, typeof(CancelActivity));
i.SetFlags(ActivityFlags.NewTask);
this.StartActivity(i);
}
}
and use in CancelActivity like below example
[Activity(Label = "CancelActivity")]
public class CancelActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.cancel);
this.FindViewById(Resource.Id.cancelButtonYes).Click += delegate
{
var password = this.FindViewById(Resource.Id.cancelPassword);
// call KillAlert method from GeneralServic
GeneralService.generalService.KillAlert(password.TEXT);
};
}
}

Xamarin Android possible ScreenOrientation.Landscape bug

my app had some strange behaviour so I recreated the app until the point where it bugged and found out that ScreenOrientation.Landscape is the culprit.
If you make a new blank app in visual studio 15 and replace the MainActivity with:
[Activity(Label = "TestLandscapeBug", MainLauncher = true, Icon = "#drawable/icon",
ScreenOrientation = ScreenOrientation.Landscape)]
public class MainActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
System.Console.WriteLine("OnCreate");
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
}
protected override void OnDestroy()
{
System.Console.WriteLine("OnDestroy");
base.OnDestroy();
}
protected override void OnPause()
{
System.Console.WriteLine("OnPause");
base.OnPause();
}
protected override void OnRestart()
{
System.Console.WriteLine("OnRestart");
base.OnRestart();
}
protected override void OnResume()
{
System.Console.WriteLine("OnResume");
base.OnResume();
}
protected override void OnStart()
{
System.Console.WriteLine("OnStart");
base.OnStart();
}
protected override void OnStop()
{
System.Console.WriteLine("OnStop");
base.OnStop();
}
}
run the app and press the sleep button:
OnPause, OnStop, OnDestroy, OnCreate, OnStart, OnResume and OnPause are called.
if you remove ScreenOrientation = ScreenOrientation.Landscape OnPause and OnStop are called.
Is this a bug? Or am I doing something wrong?
how can I fix this or use something else which locks the screen in landscape.
It's normal for an activity to switch to portrait mode when the screen is locked. Whenever the orientation changes, OnDestroy is called followed by OnCreate. So, there's nothing to worry about as what you're witnessing is default behavior of Android.
Portrait is kind of the default orientation for the lockscreen so it makes sense that your activity also switches to that when locking.

You MUST call Xamarin.Forms.Init(); prior to using it

In my app.xaml.cs I create a new page.
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new WrapLayoutPage());
}
This page calls a static class, which uses the DependencyService to perform some tasks.
The line which throws the error:
var tmpTable = SqLiteHelper.GetItem<TableX>("someId");
SqLiteHelper:
public static class SqLiteHelper
{
private static readonly SQLiteConnection DatabaseConnection = DependencyService.Get<ISqLite>().GetConnection();
private static readonly object Locker = new object();
public static DbObjectV3 GetItem<T>(Guid inId) where T : DbObjectV3, new()
{
lock (Locker)
{
var tmpItem = DatabaseConnection.Table<T>().FirstOrDefault(inItem => inItem.Id == inId);
tmpItem.IsNewObject = false;
return tmpItem;
}
}
}
This throws me a TypeInitializationException with the InnerException:
You MUST call Xamarin.Forms.Init(); prior to using it
It's somehow related to the static helper class, because prior to that call, I can use the DependencyService without any problems!
As mainlauncher I'm using a splash screen. In this class I do some startup work, which relies on the DependencyService.
SplashScreen:
[Activity(Theme = "#style/MyTheme.Splash", NoHistory = true, MainLauncher = true)]
public class SplashScreen : Activity
{
static readonly string TAG = "X:" + typeof(SplashScreen).Name;
public override void OnCreate(Bundle savedInstanceState, PersistableBundle persistentState)
{
base.OnCreate(savedInstanceState, persistentState);
Log.Debug(TAG, "SplashActivity.OnCreate");
}
}
My MainActivity:
[Activity(Label = "FrameworkForms", Icon = "#drawable/icon", ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, Theme = "#style/MainActivityTheme", MainLauncher = false)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
Xamarin.Forms.Forms.Init(this, bundle);
App.ScreenWidth = (double)(Resources.DisplayMetrics.WidthPixels / Resources.DisplayMetrics.Density);
LoadApplication(new App());
}
}
Now after changing the Activity in SplashScreen to FormsAppCompatActivity, I get another error.
Call Forms.Init() before Hide Keyboard
What's the thing here?
This is pretty unfortunate. I used the wrong OnCreate() method in my SplashScreen.
I changed SplashScreen to:
[Activity(Theme = "#style/MyTheme.Splash", NoHistory = true, MainLauncher = true)]
public class SplashScreen : Activity
{
static readonly string TAG = "X:" + typeof(SplashScreen).Name;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Forms.Forms.Init(this, savedInstanceState);
Log.Debug(TAG, "SplashActivity.OnCreate");
}
protected override void OnResume()
{
base.OnResume();
Task tmpStartupWork = new Task(() =>
{
Log.Debug(TAG, "Performing some startup work that takes a bit of time.");
StartUpTasks.InitializeDatabaseCreation();
Log.Debug(TAG, "Working in the background - important stuff.");
});
tmpStartupWork.ContinueWith(inT =>
{
Log.Debug(TAG, "Work is finished - start MainActivity.");
StartActivity(new Intent(Application.Context, typeof(MainActivity)));
}, TaskScheduler.FromCurrentSynchronizationContext());
tmpStartupWork.Start();
}
}
Unfortunately, the documentation on Xamarin about creating a splash screen uses the OnCreate() method with 2 parameters!
I also came across this mistake. The cause of the error was that I wanted to change my namespace. From
public partial class AppDelegate :
global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
I changed it to:
using Xamarin.Forms.Platform.iOS;
public partial class AppDelegate : FormsApplicationDelegate
Then I saw the following:
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
I thought, it would be useful to adjust that as well... without thinking more exactly about it. And I wrote:
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
this.Init();
A while later, I got the mistake and had to laboriously search for the cause. Finally I found and fixed it. Maybe it can help someone :-) Cheers...

How to resolve registered services(worker objects) in an android service using mvvmcross?

I'm trying to use mvvmcross in a android service, this is the code :
public interface IMyWorkerObject
{
void ShowNotificationIcon();
}
[Service]
public class MyAndroidService : Service
{
private Thread myThread;
public override void OnCreate()
{
myThread = new Thread(() =>
{
do
{
var myWorkerObject = Mvx.Resolve<IMyWorkerObject>();
myWorkerObject.ShowNotificationIcon();
Thread.Sleep(10000);
} while (true);
});
myThread.IsBackground = true;
myThread.Start();
base.OnCreate();
}
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
return StartCommandResult.Sticky;
}
public override void OnDestroy()
{
if (myThread != null)
{
if (myThread.IsAlive)
myThread.Abort();
myThread = null;
}
}
public override IBinder OnBind(Intent intent)
{
return null;
}
}
and I have this code in my app.cs
public class App : MvxApplication
{
// ... some code ...
public override void Initialize()
{
base.Initialize();
//Initializing Plugins..
PluginLoader.Instance.EnsureLoaded();
//Initializing Work Objects..
var services = CreatableTypes()
.EndingWith("Service");
// start android service
var context = Application.Context;
var intent = new Intent(Application.Context, typeof(MyAndroidService ));
context.StartService(intent);
//Initializing Start Navigation
RegisterAppStart<MainViewModel>();
}
// ... some code ...
}
everything works fine until I fully close the application, because of returning StartCommandResult.Sticky
the android service restarts but throws an exception at var myWorkerObject = Mvx.Resolve<IMyWorkerObject>(); in the OnCreate method :
Error : System.NullReferenceException: Object reference not set to an
instance of an object at
Cirrious.CrossCore.Mvx.Resolve[IMyWorkerObject] () [0x00000] in
:0 at App1.MyAndroidService.b__0 ()
[0x00000] in :0
what am i doing wrong, or better say is there any samples on how to use android services with mvvmcross?

Categories

Resources