Start specific view of Gluon App from a notification - gluon

I set up an alarm to show a corresponding Notification. The PendingIntent of the Notification is used to start the Gluon App main class. To show a View other than the homeView, I call switchView(otherView) in the postInit method. OtherView is shown, but without AppBar. While it's possible to make the AppBar appear, I wonder if this is the right approach.
#Override
public void postInit(Scene scene) {
// additional setUp logic
boolean showReadingView = (boolean) PlatformProvider.getPlatform().getLaunchIntentExtra("showReadingView", false);
if (showReadingView) {
switchView(READING_VIEW);
}
}

When triggering anything related to the JavaFX thread from another thread, we have to use Platform.runLater().
Yours is a clear case of this situation: the Android thread is calling some pending intent, and as a result, the app is started again.
This should be done:
#Override
public void postInit(Scene scene) {
// additional setUp logic
boolean showReadingView = (boolean) PlatformProvider.getPlatform().getLaunchIntentExtra("showReadingView", false);
if (showReadingView) {
Platform.runLater(() -> switchView(READING_VIEW));
}
}

Related

How to create a never ending background service in Xamarin.Forms?

I am monitoring the user's location every 15 minutes and I just want the application to continue sending the location even if the user closes the application in the taskbar.
I tried this sample but it's in Xamarin.Android https://learn.microsoft.com/en-us/xamarin/android/app-fundamentals/services/foreground-services i have to create a dependencyservice but i don't know how.
i have to create a dependencyservice but i don't know how.
First, create an Interface in the Xamarin.forms project:
public interface IStartService
{
void StartForegroundServiceCompat();
}
And then create a new file let's call it itstartServiceAndroid in xxx.Android project to implement the service you want:
[assembly: Dependency(typeof(startServiceAndroid))]
namespace DependencyServiceDemos.Droid
{
public class startServiceAndroid : IStartService
{
public void StartForegroundServiceCompat()
{
var intent = new Intent(MainActivity.Instance, typeof(myLocationService));
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
MainActivity.Instance.StartForegroundService(intent);
}
else
{
MainActivity.Instance.StartService(intent);
}
}
}
[Service]
public class myLocationService : Service
{
public override IBinder OnBind(Intent intent)
{
}
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
// Code not directly related to publishing the notification has been omitted for clarity.
// Normally, this method would hold the code to be run when the service is started.
//Write want you want to do here
}
}
}
Once you want to call the StartForegroundServiceCompat method in Xamarin.forms project, you can use:
public MainPage()
{
InitializeComponent();
//call method to start service, you can put this line everywhere you want to get start
DependencyService.Get<IStartService>().StartForegroundServiceCompat();
}
Here is the document about dependency-service
For iOS, if the user closes the application in the taskbar, you will no longer be able to run any service. If the app is running, you can read this document about ios-backgrounding-walkthroughs/location-walkthrough
You might want to have a look at Shiny by Allan Ritchie. It's currently in beta but I would still suggest using it, as it will save you a lot of trouble writing this code yourself. Here's a blog post by Allan, explaining what you can do with Shiny in terms of background tasks - I think Scheduled Jobs are the thing you're looking for.

How to implement an Alert box in xamarin forms android

I implemented a Dependency Service to display alert box in my xamarin forms app.My app crashes when I call the alert box in android.
Here is My code
Android.App.AlertDialog.Builder _dialog = new AlertDialog.Builder(Android.App.Application.Context);
AlertDialog _alertDialog = _dialog.Create();
_alertDialog.SetTitle("Unauthorized");
_alertDialog.SetMessage("Please login again to continue using the App);
_alertDialog.SetButton("OK", (c, ev) => { CloseApp(); });
_alertDialog.Show();
It throws an exception:-Unable to add window -- token null is not for an application in android.
How to fix this Please help me
Unable to add window -- token null is not valid; is your activity running?
You are using a Application context and you need to use an Activity based one.
So you need the current Activity's context within your Forms' dependancy class which you can obtain that via multiple methods; A static var on the MainActivity, using the "CurrentActivityPlugin", etc...
As a quick fix, add a static Context variable to your MainActivity class and set it in the OnResume override.
public static Context context;
protected override void OnResume()
{
context = this;
base.OnResume();
}
Then change your context to that static one:
Android.App.AlertDialog.Builder _dialog = new Android.App.AlertDialog.Builder(MainActivity.context);

OnAppearing different on iOS and Android

I have found that on iOS, OnAppearing is called when the page literally appears on the screen, whereas on Android, it's called when it's created.
I'm using this event to lazily construct an expensive to construct view but obviously the Android behaviour defeats this.
Is there some way of knowing on Android when a screen literally appears on the screen?
You can use the event:
this.Appearing += YourPageAppearing;
Otherwise, you should use the methods of the Application class that contains the lifecycle methods:
protected override void OnStart()
{
Debug.WriteLine ("OnStart");
}
protected override void OnSleep()
{
Debug.WriteLine ("OnSleep");
}
protected override void OnResume()
{
Debug.WriteLine ("OnResume");
}
On Android, Xamarin.Forms.Page.OnAppearing is called immediately before the page's view is shown to user (not when the page is "created" (constructed)).
If you want an initial view to appear quickly, by omitting an expensive sub-view, use a binding to make that view's IsVisible initially be "false". This will keep it out of the visual tree, avoiding most of the cost of building it. Place the (invisible) view in a grid cell, whose dimensions are constant (either in DPs or "*" - anything other than "Auto".) So that layout will be "ready" for that view, when you make it visible.
APPROACH 1:
Now you just need a binding in view model that will change IsVisible to "true".
The simplest hack is to, in OnAppearing, fire an action that will change that variable after 250 ms.
APPROACH 2:
The clean alternative is to create a custom page renderer, and override "draw".
Have draw, after calling base.draw, check an action property on your page.
If not null, invoke that action, then clear it (so only happens once).
I do this by inheriting from a custom page base class:
XAML for each of my pages (change "ContentPage" to "exodus:ExBasePage"):
<exodus:ExBasePage
xmlns:exodus="clr-namespace:Exodus;assembly=Exodus"
x:Class="YourNamespace.YourPage">
...
</exodus:ExBasePage>
xaml.cs:
using Exodus;
// After creating page, change "ContentPage" to "ExBasePage".
public partial class YourPage : ExBasePage
{
...
my custom ContentPage. NOTE: Includes code not needed for this, related to iOS Safe Area and Android hardward back button:
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace Exodus
{
public abstract partial class ExBasePage : ContentPage
{
public ExBasePage()
{
// Each sub-class calls InitializeComponent(); not needed here.
ExBasePage.SetupForLightStatusBar( this );
}
// Avoids overlapping iOS status bar at top, and sets a dark background color.
public static void SetupForLightStatusBar( ContentPage page )
{
page.On<Xamarin.Forms.PlatformConfiguration.iOS>().SetUseSafeArea( true );
// iOS NOTE: Each ContentPage must set its BackgroundColor to black or other dark color (when using LightContent for status bar).
//page.BackgroundColor = Color.Black;
page.BackgroundColor = Color.FromRgb( 0.3, 0.3, 0.3 );
}
// Per-platform ExBasePageRenderer uses these.
public System.Action NextDrawAction;
/// <summary>
/// Override to do something else (or to do nothing, i.e. suppress back button).
/// </summary>
public virtual void OnHardwareBackButton()
{
// Normal content page; do normal back button behavior.
global::Exodus.Services.NavigatePopAsync();
}
}
}
renderer in Android project:
using System;
using Android.Content;
using Android.Views;
using Android.Graphics;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Exodus;
using Exodus.Android;
[assembly: ExportRenderer( typeof( ExBasePage ), typeof( ExBasePageRenderer ) )]
namespace Exodus.Android
{
public class ExBasePageRenderer : PageRenderer
{
public ExBasePageRenderer( Context context ) : base( context )
{
}
protected override void OnElementChanged( ElementChangedEventArgs<Page> e )
{
base.OnElementChanged( e );
var page = Element as ExBasePage;
if (page != null)
page.firstDraw = true;
}
public override void Draw( Canvas canvas )
{
try
{
base.Draw( canvas );
var page = Element as ExBasePage;
if (page?.NextDrawAction != null)
{
page.NextDrawAction();
page.NextDrawAction = null;
}
}
catch (Exception ex)
{
// TBD: Got Disposed exception on Android Bitmap, after rotating phone (in simulator).
// TODO: Log exception.
Console.WriteLine( "ExBasePageRenderer.Draw exception: " + ex.ToString() );
}
}
}
}
To do some action after the first time the page is drawn:
public partial class YourPage : ExBasePage
{
protected override void OnAppearing()
{
// TODO: OnPlatform code - I don't have it handy.
// On iOS, we call immediately "DeferredOnAppearing();"
// On Android, we set this field, and it is done in custom renderer.
NextDrawAction = DeferredOnAppearing;
}
void DeferredOnAppearing()
{
// Whatever you want to happen after page is drawn first time:
// ((MyViewModel)BindingContext).ExpensiveViewVisible = true;
// Where MyViewModel contains:
// public bool ExpensiveViewVisible { get; set; }
// And your XAML contains:
// <ExpensiveView IsVisible={Binding ExpensiveViewVisible}" ... />
}
}
NOTE: I do this differently on iOS, because Xamarin Forms on iOS (incorrectly - not to spec) calls OnAppearing AFTER the page is drawn.
So I have OnPlatform logic. On iOS, OnAppearing immediately calls DeferredOnAppearing. On Android, the line shown is done.
Hopefully iOS will eventually be fixed to call OnAppearing BEFORE,
for consistency between the two platforms.
If so, I would then add a similar renderer for iOS.
(The current iOS implementation means there is no way to update a view before it appears a SECOND time, due to popping the nav stack.
instead, it appears with outdated content, THEN you get a chance
to correct it. This is not good.)

Xamarin Mac KVO model bindings - change fires twice

I am trying to implement KVO bindings in a Xamarin Mac desktop app.
I have followed the docs, and it is working, but the bindings appear to trigger 2 change events each time!
If I create a KVO model with a binding like this...
private int _MyVal;
[Export("MyVal")]
public int MyVal
{
get { return _MyVal; }
set
{
WillChangeValue("MyVal");
this._MyVal = value;
DidChangeValue("MyVal");
}
}
And bind a control to it in Xcode under the bindings section with the path self.SettingsModel.MyValue
It all appears to work fine, the control shows the model value, changing the model value programmatically updates the control and changing the control updates the model value.
However, it runs the change event twice.
I am listening to the change so I can then hit an API with the value.
SettingsModel.AddObserver(this, (NSString)key, NSKeyValueObservingOptions.New, this.Handle);
Then later...
public override void ObserveValue(NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
{
switch (keyPath)
{
case "MyValue":
// CODE HERE THAT UPDATES AN API WITH THE VALUE
// But this handler fires twice.
break;
}
}
Im not sure if its Xamarin or XCode that is causing the double trigger.
Interestingly, if you don't specify the Xcode WillChangeValue and DidChangeValue methods, then it doesn't trigger twice - as though Xamarin has automatically triggered the change once. However, it no longer triggers a change when programmatically updating the model value...
[Export("MyVal")]
public int MyVal { get; set }
The above will work for the Xcode controls, they will update the model and trigger a change event.
But programmatically updating it
this.SettingsModel.MyVal = 1;
Does not trigger the change event.
It's very confusing, any idea on how to stop 2 change events firing, as I don't want to hit the API twice every time!
When it fires twice, the stack trace (abridged) for the first has...
MainViewController.ObserveValue
ObjCRuntime.Messaging.void_objc_msgSendSuper_IntPtr()
Foundation.NSObject.DidChangeValue(string forKey)
CameraSettingsModel.set_MyValue(int value)
AppKit.NSApplication.NSApplicationMain()
AppKit.NSApplication.Main(string[] args)
MainClass.Main(string[] args)
Which looks fine, but the second...
MainViewController.ObserveValue
AppKit.NSApplication.NSApplicationMain()
AppKit.NSApplication.Main(string[] args)
MainClass.Main(string[] args)
Has no mention of the Setting Model triggering the event
You are hitting this - Receiving 2 KVO notifications for a single KVC change
and need to override AutomaticallyNotifiesObserversForKey it appears.
Cocoa is "doing you a favor" by sending the notifications for you, which is great except you have the managed version also sending notifications.
It looks something like this:
[Export ("automaticallyNotifiesObserversForKey:")]
public static new bool AutomaticallyNotifiesObserversForKey (string key) => false;
bool _checkValue;
[Export("CheckValue")]
public bool CheckValue
{
get { return _checkValue; }
set
{
WillChangeValue("CheckValue");
_checkValue = value;
DidChangeValue("CheckValue");
}
}
public override void ViewDidLoad ()
{
base.ViewDidLoad();
this.AddObserver("CheckValue", NSKeyValueObservingOptions.New, o =>
{
Console.WriteLine($"Observer triggered for {o}");
});
CheckValue = false;
}

A while after I deploy my code to iOS the phone hangs up

Is there some way I can track what's happening with Xamarin? I do a debug with a target of my phone and then later it hangs up. I can't do anything, can't shut it down with the button on the side and the only way I can get the phone to work again is by pressing the button on the side and the home button. Running on iPhone 6s Plus.
Here is some code that I suspect might be causing a problem. Would also like to know if anyone can see anything that might cause the problem with the code:
public partial class App : Application
{
public static DataManager db;
private static Stopwatch stopWatch = new Stopwatch();
private const int defaultTimespan = 1;
public App()
{
InitializeComponent();
}
public static DataManager DB
{
get
{
if (db == null)
{
db = new DataManager();
}
return db;
}
}
protected override void OnStart()
{
App.DB.InitData();
MainPage = new Japanese.MainPage();
if (!stopWatch.IsRunning)
stopWatch.Start();
Device.StartTimer(new TimeSpan(0, 0, 1), () =>
{
if (stopWatch.IsRunning && stopWatch.Elapsed.Minutes >= defaultTimespan)
{
Debug.WriteLine("Checking database");
PointChecker.CheckScore();
stopWatch.Restart();
}
return true;
});
}
protected override void OnSleep()
{
Debug.WriteLine("OnSleep");
stopWatch.Reset();
}
protected override void OnResume()
{
Debug.WriteLine("OnResume");
// deductPoints();
stopWatch.Start();
}
}
iOS requires that everything is setup, with 17 seconds, on the initial first load. This means that you must set the MainPage in your App constructor, you can't set it in OnStart.
Or, you can place MainPage = new ContentPage(); in your App constructor, then it will be replaced in OnStart. However, you must set the MainPage, when it's constructing the Application.
Android and UWP I think, give you some freedom, and you can set it in OnStart, but definitely not iOS.
My iPhones are hangs up when I have debugger connected to running app and that connection is interrupted. For example, if you unplug lightning cable while Visual Studio is debugging - the phone will hangs.
So try to start your application from phone(without debugger attached) and check your datacable.

Resources