Hi I am trying to build an application in xamarin forms using PCL. I am trying to logout user from my app if the app is idle more than 10minute or more. I tried it by events that are called on the time when app is about to go to sleep state. But if the device screentimeout is set for never timeout then maybe It will never go to sleep. So how can I achieve this. I am new to xamarin forms. And as I am building the app for all the platforms I am confused how to manage this timeout?
Tweaked @Wolfgang version
public sealed class SessionManager
{
static readonly Lazy<SessionManager> lazy =
new Lazy<SessionManager>(() => new SessionManager());
public static SessionManager Instance { get { return lazy.Value; } }
private Stopwatch StopWatch = new Stopwatch();
SessionManager()
{
SessionDuration = TimeSpan.FromMinutes(5);
}
public TimeSpan SessionDuration;
public void EndTrackSession()
{
if (StopWatch.IsRunning)
{
StopWatch.Stop();
}
}
public void ExtendSession()
{
if (StopWatch.IsRunning)
{
StopWatch.Restart();
}
}
public void StartTrackSessionAsync()
{
if (!StopWatch.IsRunning)
{
StopWatch.Restart();
}
Xamarin.Forms.Device.StartTimer(new TimeSpan(0, 0, 2), () =>
{
if (StopWatch.IsRunning && StopWatch.Elapsed.Minutes >= SessionDuration.Minutes)
{
Xamarin.Forms.Device.BeginInvokeOnMainThread(async () =>
{
await Prism.PrismApplicationBase.Current.Container.Resolve<INavigationService>().NavigateAsync("/Index/Navigation/LoginPage");
});
StopWatch.Stop();
}
return true;
});
}
}
Under main activity added the below
public override void OnUserInteraction()
{
base.OnUserInteraction();
SessionManager.Instance.ExtendSession();
}
For now I use the following approach. Might need to do some additional testing to make sure everything works as expected. For example I am not sure what will happen if the app (iOS or Android) is in the background for extended amounts of time. Will the timer still be called every second or not? Perhaps when using timers with a short enough expiration time (~5 minutes) this is no issue whatsoever? Etc...
I've based my approach on several pieces of code I found on the web (some Xamarin code, some Swift / Java code) - there didn't seem to be a good comprehensive solution.
Anyways, some preliminary testing suggests this approach works fine.
First I've created a singleton class called SessionManager
. This class contains a timer (actually just a while loop that sleeps every second) and methods to start, stop and extend the timer. It will also fire an event if the session expiration timer is expired.
public sealed class SessionManager
{
static readonly Lazy<SessionManager> lazy =
new Lazy<SessionManager>(() => new SessionManager());
public static SessionManager Instance { get { return lazy.Value; } }
SessionManager() {
this.SessionDuration = TimeSpan.FromMinutes(5);
this.sessionExpirationTime = DateTime.FromFileTimeUtc(0);
}
/// <summary>
/// The duration of the session, by default this is set to 5 minutes.
/// </summary>
public TimeSpan SessionDuration;
/// <summary>
/// The OnSessionExpired event is fired when the session timer expires.
/// This event is not fired if the timer is stopped manually using
/// EndTrackSession.
/// </summary>
public EventHandler OnSessionExpired;
/// <summary>
/// The session expiration time.
/// </summary>
DateTime sessionExpirationTime;
/// <summary>
/// A boolean value indicating wheter a session is currently active.
/// Is set to true when StartTrackSessionAsync is called. Becomes false if
/// the session is expired manually or by expiration of the session
/// timer.
/// </summary>
public bool IsSessionActive { private set; get; }
/// <summary>
/// Starts the session timer.
/// </summary>
/// <returns>The track session async.</returns>
public async Task StartTrackSessionAsync() {
this.IsSessionActive = true;
ExtendSession();
await StartSessionTimerAsync();
}
/// <summary>
/// Stop tracking a session manually. The OnSessionExpired will not be
/// called.
/// </summary>
public void EndTrackSession() {
this.IsSessionActive = false;
this.sessionExpirationTime = DateTime.FromFileTimeUtc(0);
}
/// <summary>
/// If the session is active, then the session time is extended based
/// on the current time and the SessionDuration.
/// duration.
/// </summary>
public void ExtendSession()
{
if (this.IsSessionActive == false) {
return;
}
this.sessionExpirationTime = DateTime.Now.Add(this.SessionDuration);
}
/// <summary>
/// Starts the session timer. When the session is expired and still
/// active the OnSessionExpired event is fired.
/// </summary>
/// <returns>The session timer async.</returns>
async Task StartSessionTimerAsync() {
if (this.IsSessionActive == false) {
return;
}
while (DateTime.Now < this.sessionExpirationTime) {
await Task.Delay(1000);
}
if (this.IsSessionActive && this.OnSessionExpired != null) {
this.IsSessionActive = false;
this.OnSessionExpired.Invoke(this, null);
}
}
}
For the Android app I then:
Configure the SessionManager
in the MainActivity to logout when the session expires.
Override the OnUserInteraction
method in the MainActivity
to extend the session timer on user interaction.
public class MainActivity /* ... */ {
protected override void OnCreate(Bundle bundle)
{
// ...
SessionManager.Instance.SessionDuration = TimeSpan.FromSeconds(10);
SessionManager.Instance.OnSessionExpired = HandleSessionExpired;
}
public override void OnUserInteraction()
{
base.OnUserInteraction();
SessionManager.Instance.ExtendSession();
}
async void HandleSessionExpired(object sender, EventArgs e)
{
await App.Instance.DoLogoutAsync();
}
}
For iOS I do the following:
Configure the SessionManager
in the AppDelegate to logout when the session expires.
Add a custom gesture handler to the key window to extend the session timer on user interaction.
public partial class AppDelegate /* ... */
{
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
// ...
var success = base.FinishedLaunching(app, options);
if (success) {
SessionManager.Instance.SessionDuration = TimeSpan.FromSeconds(10);
SessionManager.Instance.OnSessionExpired += HandleSessionExpired;
var allGesturesRecognizer = new AllGesturesRecognizer(delegate
{
SessionManager.Instance.ExtendSession();
});
this.Window.AddGestureRecognizer(allGesturesRecognizer);
}
return success;
}
async void HandleSessionExpired(object sender, EventArgs e)
{
await App.instance.DoLogoutAsync();
}
class AllGesturesRecognizer: UIGestureRecognizer {
public delegate void OnTouchesEnded();
private OnTouchesEnded touchesEndedDelegate;
public AllGesturesRecognizer(OnTouchesEnded touchesEnded) {
this.touchesEndedDelegate = touchesEnded;
}
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
this.State = UIGestureRecognizerState.Failed;
this.touchesEndedDelegate();
base.TouchesEnded(touches, evt);
}
}
}
Edit: Bolo asked a good question below, so I'll add it here. StartTrackSessionAsync is called as soon as the user is logged in. EndTrackSession should be called when the user is logged out of the app as well of course.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With