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();
}
}
}
You can try using this class I found, it covers some of the limits to the DeviceTimer:
public class MySystemDeviceTimer
{
private readonly TimeSpan timespan;
private readonly Action callback;
private CancellationTokenSource cancellation;
public bool running { get; private set; }
public MySystemDeviceTimer(TimeSpan timespan, Action callback)
{
this.timespan = timespan;
this.callback = callback;
this.cancellation = new CancellationTokenSource();
}
public void Start()
{
running = true;
start(true);
}
private void start(bool continuous)
{
CancellationTokenSource cts = this.cancellation; // safe copy
Device.StartTimer(this.timespan,
() =>
{
if (cts.IsCancellationRequested)
{
running = false;
return false;
}
this.callback.Invoke();
return continuous;
});
}
public void FireOnce()
{
running = true;
start(false);
running = false;
}
public void Stop()
{
Interlocked.Exchange(ref this.cancellation, new CancellationTokenSource()).Cancel();
}
}
Then for your purpose:
MySystemDeviceTimer timer;
if (timer == null)
{
timer = new MySystemDeviceTimer(TimeSpan.FromSeconds(5), SendReadConfirmation);
timer.FireOnce();
}
else if (timer.running)
timer.Stop();
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