0

I am working on a Xamarin Apps. One of the requirements is to logout user after certain period of time. I put a timer that runs every 30 mins to tell the user that his session expired and give him option to continue his session or logout. If he decided to continue his session, the timer will continue to run every 30 minutes, but if he decided to end his session the timer will stop.
The timer in Xamarin doesn't honor the time you set on the StartTimer because it runs even before the 30 minutes.

App.xaml.cs

public App()
{
    InitializeComponent();                               
    MainPage = new NavigationPage(new LoginPage());   
} 

LoginPage.xaml.cs

public LoginPage()
    {           
        InitializeComponent();
        
        CheckUserLoginStatus();          
    }

  public async void CheckUserLoginStatus()
    {           
        var msalService = new MsalAuthService();

        if (await msalService.SignInAsync())
        {
            Settings.UserLastLogin = DateTime.Now;
            App.Current.MainPage = new Master();
        }
    }

MsalAuthService.cs

public async Task<bool> SignInAsync()
    {
        try
        {
            // login logic here 
            //after successfull login start timer
           

Device.StartTimer(new TimeSpan(0, 30, 0), () =>
                {
                    // do something every 30 minutes
                    Device.BeginInvokeOnMainThread(async () =>
                    {
                        // interact with UI elements
                        await AutoLogout();
                    });
                    var accessToken = ApplicationPropertyProvider.GetProperty<string>(Commons.AUTH_TOKEN);
                    return !string.IsNullOrEmpty(accessToken);
                });
                Application.Current.Properties["USERLOGINDATE"] = DateTime.Now;
                return !string.IsNullOrEmpty(accessToken);
               }
            return true;
        }
        
        catch (Exception ex)
        {
            Debug.WriteLine(ex.ToString());
            return false;
        }
    } 

 
ramdev
  • 137
  • 1
  • 9
  • to start with, use `System.Timers` instead of `Device.StartTimer` – Jason May 19 '22 at 14:43
  • @Jason, Will that work in iOS? What are their difference? Thanks – ramdev May 19 '22 at 15:05
  • `System.Timers` is a core .NET class. `Device.StartTimer` was added to Xamarin Forms years ago when `System.Timers` wasn't supported. – Jason May 19 '22 at 15:16
  • I tried System.Timers but it doesn't behave well. I set up 30 mins but it start multiple times even if the 30 mins is not yet completed – ramdev May 19 '22 at 18:17
  • There must be something else going on. Where are you setting up the timer? – Jason May 19 '22 at 18:19
  • I set it after a login is successful. ` var timer = new Timer(); timer.Interval = 20000; // I tried it for twenty seconds for now timer.Elapsed += AutoLogoutTimerElapsed; timer.Start(); `, – ramdev May 19 '22 at 18:23
  • 1
    that doesn't tell me anything useful. Is it running the page, or the App class, or something else? Is there any possibility that more than one instance is running? Do you pause the timer when it displays the UI after firing? Please update your question with the relevant code – Jason May 19 '22 at 18:28
  • @ramdev, I recommend first testing with `await AutoLogout();` **commented out.** Until you've resolved the bug "running more than once". **THEN** you'll need to rewrite how you interact with AutoLogout. Whatever is inside `BeginInvoke...` won't start until **after** the surrounding method finishes. So it won't start until after `return !string.IsNullOrEmpty(accessToken);`. I assume that isn't what you want, so you'll have to do it differently - but first comment that out, and find out what is wrong with the timer. – ToolmakerSteve May 19 '22 at 18:36
  • @Jason, adding some code – ramdev May 19 '22 at 19:45
  • @ToolmakerSteve, I tried without async and it looks working ok. But if you add async it change behaviour. I need async for some reason. – ramdev May 19 '22 at 19:46
  • If you comment out the `BeginInvoke` block and everything inside (4 or 5 lines total), and instead do `Debug.WriteLine("--- Timer ---");`, does it print ONCE every 20 seconds (or whatever time interval you have)? – ToolmakerSteve May 19 '22 at 20:06
  • @ToolmakerSteve, yes it is printing ONCE every 20 seconds – ramdev May 20 '22 at 00:33

1 Answers1

1

Its possible to periodically interact with user, without a timer:

using System.Diagnostics;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace XFSOAnswers
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class PeriodicInteractPage : ContentPage
    {
        public PeriodicInteractPage()
        {
            InitializeComponent();
            StartPeriodicTask(5, GetUserConfirmation, Done);
        }

        delegate Task<bool> TaskBoolDeleg();
        delegate Task TaskDeleg();

        private void StartPeriodicTask(float seconds, TaskBoolDeleg periodicTask, TaskDeleg doneTask)
        {
            // On MainThread, so can interact with user.
            // "async" + "await Delay" ensures UI is not blocked until time is up.
            Device.BeginInvokeOnMainThread(async () =>
            {
                bool alive = true;
                while (alive)
                {
                    await Task.Delay((int)(1000 * seconds));
                    alive = await periodicTask();
                }
                await doneTask();
            });
        }

        private async Task<bool> GetUserConfirmation()
        {
            // Block UI until user responds.
            bool answer = await DisplayAlert("", "Are you still there?", "Yes", "No");
            Debug.WriteLine($"--- Still there: {answer} ---");
            return answer;
        }

        private async Task Done()
        {
            Debug.WriteLine($"--- DONE ---");
            await DisplayAlert("", "Logout", "OK");
        }
    }
}

StartPeriodicTask(5, GetUserConfirmation, Done); will call GetUserConfirmation after 5 seconds. Increase as desired.
If user responds "Yes", then the time delay starts again.
If user responds "No", then loop exits, Done runs.

CREDIT: Based loosely on the "timerless" approach in https://stackoverflow.com/a/45364589/199364.

ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196