Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Xamarin forms: Check if user inactive after some time log out app

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?

like image 812
Sonali Avatar asked Jan 04 '17 14:01

Sonali


Video Answer


2 Answers

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();
    }
like image 53
Kwazi Lamula Avatar answered Oct 18 '22 12:10

Kwazi Lamula


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.

like image 40
Wolfgang Schreurs Avatar answered Oct 18 '22 14:10

Wolfgang Schreurs