I am working on a Chat app. After the messages of a chat are loaded and the messages were visible for 5 seconds, I want to send a read confirmation to the server. This is what I've come up with so far:
public async void RefreshLocalData()
{
// some async code to load the messages
if (_selectedChat.countNewMessages > 0)
{
Device.StartTimer(TimeSpan.FromSeconds(5), SendReadConfirmation);
}
}
When RefreshLocalData()
is called, I know that either another chat was selected by the user or new messages came in for the current chat. So when RefreshLocalData()
is called, I have to cancel the current timer to start a new one.
Another situation where I have to cancel the timer is when I navigate to another Page
. This is no problem, because the whole ViewModel
is disposed when this happens.
With the code above, if RefreshLocalData()
is called again but the stated TimeSpan
of 5 seconds is not over yet, the method is still executing.
Is there a way to cancel the timer (if RefreshLocalData()
is called again)?
I have found this answer in the Xamarin forum: https://forums.xamarin.com/discussion/comment/149877/#Comment_149877
I have changed it a little bit to meet my needs and this solution is working:
public class StoppableTimer
{
private readonly TimeSpan timespan;
private readonly Action callback;
private CancellationTokenSource cancellation;
public StoppableTimer(TimeSpan timespan, Action callback)
{
this.timespan = timespan;
this.callback = callback;
this.cancellation = new CancellationTokenSource();
}
public void Start()
{
CancellationTokenSource cts = this.cancellation; // safe copy
Device.StartTimer(this.timespan,
() => {
if (cts.IsCancellationRequested) return false;
this.callback.Invoke();
return false; // or true for periodic behavior
});
}
public void Stop()
{
Interlocked.Exchange(ref this.cancellation, new CancellationTokenSource()).Cancel();
}
public void Dispose()
{
}
}
And this is how I use it in the RefreshLocalData()
method:
private StoppableTimer stoppableTimer;
public async void RefreshLocalData()
{
if (stoppableTimer != null)
{
stoppableTimer.Stop();
}
// some async code to load the messages
if (_selectedChat.countNewMessages > 0)
{
if (stoppableTimer == null)
{
stoppableTimer = new StoppableTimer(TimeSpan.FromSeconds(5), SendReadConfirmation);
stoppableTimer.Start();
}
else
{
stoppableTimer.Start();
}
}
}