How to cancel a Timer before it's finished - xamarin

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();

Yes you can with Device.StartTimer() as long as you return true to have the function repeat. I typically handle this through a Boolean variable that I can control in my ViewModel. Something like below:
bool shouldRun = true;
public async void RefreshLocalData()
{
// some async code to load the messages
if (_selectedChat.countNewMessages > 0)
{
Device.StartTimer(TimeSpan.FromSeconds(5), async() =>
{
await SendReadConfirmationAsync()
return shouldRun;
});
}
}
public async Task SendReadConfirmationAsync()
{
//Do some stuff
if(we want to stop call)
shouldRun = false;
}

Related

CountDownTimer behaves weird/different the second time i open its Activity

I have been spending way too many hours on this one. And i just dont get it.
What i want: I have a main Activity (lets call it 'Activity Main') from which i am calling a second Activity ('Activity Timer') that has a CountDownTimer. Upon starting "Activity Timer" i want a Countdown to start running; it is only supposed to play a sound when it finishes. There is also a 'Pause-Button' which pauses/resumes the Countdown. 'Activity Timer' sends back results to 'Activity Main' via Intent when a button is pressed (either 'Success' or 'Fail' - well, it's a game). I am back at 'Activity Main' and all just worked perfectly fine.
That is until i start 'Activity Timer' a second time (for the secound round): The Countdown starts but cannot be paused. It just keeps ticking, even though i cancel() the Countdown and finish() the 'Activity Timer'.
Here's the code:
Activity Timer
public class GameActivity extends AppCompatActivity {
long countdown_time;
Button button_fail, button_success;
ImageButton imgbtn_pause;
boolean cd_running = false;
boolean countdown_auto = true;
TextView textView_countdown;
private static CountDownTimer;
Vibrator vibrator;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.game_activity);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// Preferences
prefs = getSharedPreferences("shared_preferences", Context.MODE_PRIVATE);
// Intent
Intent i = getIntent();
countdown_time = i.getLongExtra("countdown_time", 60000);
// Assigning
button_fail = (Button) findViewById(R.id.btn_fail);
button_success = (Button) findViewById(R.id.btn_success);
textView_countdown = (TextView) findViewById(R.id.tv_countdown);
imgbtn_pause = (ImageButton) findViewById(R.id.imgbtn_pause);
button_fail.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (cd_running) {
countdown.cancel();
}
cd_running = false;
countdown = null;
Intent returnIntent = new Intent();
if (risk) {
returnIntent.putExtra("result", "-3");
} else {
returnIntent.putExtra("result", "0");
}
setResult(Activity.RESULT_OK, returnIntent);
finish();
}
});
button_success.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (cd_running) {
countdown.cancel();
}
cd_running = false;
countdown = null;
Intent returnIntent = new Intent();
setResult(Activity.RESULT_OK, returnIntent);
finish();
}
});
imgbtn_pause.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (cd_running) {
countdown.cancel();
cd_running = false;
imgbtn_pause.setBackgroundResource(R.drawable.play_2);
} else {
startCountDownTimer();
imgbtn_risk.setVisibility(View.INVISIBLE);
imgbtn_pause.setBackgroundResource(R.drawable.pause_2);
}
}
});
private void startCountDownTimer() {
cd_running = true;
imgbtn_pause.setBackgroundResource(R.drawable.pause_2);
countdown = new CountDownTimer(countdown_time, 1000) {
public void onTick(long millisUntilFinished) {
countdown_time = millisUntilFinished;
textView_countdown.setText("" + millisUntilFinished / 1000);
}
public void onFinish() {
countdown.cancel();
countdown = null;
cd_running = false;
if (!mute) {
vibrator = (Vibrator) GameActivity.this.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(1000);
countdown_sound.start();
}
textView_countdown.setVisibility(View.INVISIBLE);
textView_gameOver.setVisibility(View.VISIBLE);
imgbtn_pause.setVisibility(View.INVISIBLE);
}
}.start();
}
The call from 'Activity Main':
#Override
public void onClick(View view) {
Intent i = new Intent(GameMenu.this, GameActivity.class);
i.putExtra("countdown_time", countdown_time);
startActivityForResult(i, 1);
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1) {
if (resultCode == Activity.RESULT_OK) {
String result_string = data.getStringExtra("result");
int result_int = Integer.parseInt(data.getStringExtra("result"));
}
if (resultCode == Activity.RESULT_CANCELED) {
// No result
}
}

Dispose Device.StartTimer, if current page is reinitialized from the mainpage

I want to stop Device.StartTimer when my current page is re-initialize from the App.cs page, but every time when page is re-initialize Device.Starttimer create new instance and function is calling multiple times.
from the links and references i get to know that it will not stop until return statement not execute, but in my case how to execute return statement outside the method, that i can not understand.
Below is my cs page code.
public partial class MediaScreen : ContentPage
{
public static readonly List<string> ImageExtensions = new List<string>
{ ".JPG", ".JPE", ".BMP", ".GIF", ".PNG" };
public static readonly List<string> videoExtensions = new List<string> {
".WAV", ".MID", ".MIDI", ".WMA", ".OGG", ".RMA", //etc
".AVI", ".MP4", ".DIVX", ".WMV", //etc
};
int i = 0;
DisplayInfo mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
public MediaScreen()
{
InitializeComponent();
try
{
StartPlaying();
}
catch (Exception e)
{
}
}
public bool StartPlaying()
{
CrossMediaManager.Current.Stop();
if (i == App.localStorage.Playlist.PlayListItems.Count)
{
i = 0;
StartPlaying();
return false;
}
var data = App.localStorage.Playlist.PlayListItems[i].MediaLibrary;
string fileName = App.path + "\\" + Path.GetFileName(data.Url);
if (ImageExtensions.Contains(Path.GetExtension(fileName).ToUpperInvariant()))
{
// process image
imgPlayer.IsVisible = true;
imgPlayer.Source = fileName;
videoPlayer.IsVisible = false;
}
else
{
try
{
CrossMediaManager.Current.Play(fileName);
CrossMediaManager.Current.MediaItemFinished += finishVideo;
videoPlayer.IsVisible = true;
imgPlayer.IsVisible = false;
}
catch (Exception e1)
{
}
}
i++;
Device.StartTimer(TimeSpan.FromSeconds
(data.Duration), StartPlaying);
return false;
}
}
}
You can use a stoppable timer instead. It has 2 goals: it can be stopped, it can be rescheduled (if it didn't start yet you can reschedule it):
Schedule example:
public StoppableTimer<object> timerScheduled; //object is param type you want
public void LaunchTimer()
{
if (timerScheduled == null)
{
timerScheduled = new StoppableTimer<object>(TimeSpan.FromSeconds(2), OnTimerStart);
timerScheduled.Start(null); //whatever param
}
else
{
//relaunch
timerScheduled.Stop();
timerScheduled.Start(control);
}
}
So when you need to cancel the timer just call timerScheduled.Stop();
Your callback:
private void OnTimerStart(object p)
{
//StartPlaying - do your stuff
}
Class:
public class StoppableTimer<T> // T is the parameter you want to pass to timer
{
private readonly TimeSpan timespan;
private readonly Action<T> callback;
private CancellationTokenSource cancellation;
public StoppableTimer(TimeSpan timespan, Action<T> callback)
{
this.timespan = timespan;
this.callback = callback;
this.cancellation = new CancellationTokenSource();
}
public void Start(T param)
{
CancellationTokenSource cts = this.cancellation; // safe copy
Device.StartTimer(this.timespan,
() => {
if (cts.IsCancellationRequested) return false;
this.callback.Invoke(param);
return false; // or true for periodic behavior
});
}
public void Stop()
{
Interlocked.Exchange(ref this.cancellation, new CancellationTokenSource()).Cancel();
}
public void Dispose()
{
}
}

How can we make app in kiosk mode using xamarin?

I'm creating new app using xamarin. I have already completed some part using some sample codes. I'm able to disable back buttons, volume buttons and power button.
But when trying to disable home button I'm getting error on debugging.
I'm following this link,Kiosk mode in Andriod.
But when trying to disable home button I'm getting error on debugging.
Since you didn't post your code and your error message, we don't know what happened, I just tried to create such a sample followed the blog your posted and it works fine by my side.
Here is the service:
namespace KioskModeAndroid
{
[Service]
[IntentFilter(new[] { "KioskModeAndroid.KioskService" })]
public class KioskService : Service
{
private static long INTERVAL = Java.Util.Concurrent.TimeUnit.Seconds.ToMillis(2);
private static string TAG = typeof(KioskService).Name;
private static string PREF_KIOSK_MODE = "pref_kiosk_mode";
private Thread t = null;
private Context ctx = null;
private bool running = false;
public override void OnDestroy()
{
Log.Info(TAG, "Stopping service 'KioskService'");
running = false;
base.OnDestroy();
}
[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
Log.Info(TAG, "Starting service 'KioskService'");
running = true;
ctx = this;
t = new Thread(() =>
{
while (running)
{
handleKioskMode();
Thread.Sleep(INTERVAL);
}
StopSelf();
});
t.Start();
return StartCommandResult.NotSticky;
}
private void handleKioskMode()
{
if (isKioskModeActive(ctx))
{
}
if (isInBackground())
{
restoreApp();
}
}
private bool isKioskModeActive(Context context)
{
var sp = PreferenceManager.GetDefaultSharedPreferences(context);
return sp.GetBoolean(PREF_KIOSK_MODE, false);
}
private bool isInBackground()
{
var am = ctx.GetSystemService(Context.ActivityService) as ActivityManager;
var processes = am.RunningAppProcesses;
foreach (var process in processes)
{
if (process.Importance == ActivityManager.RunningAppProcessInfo.ImportanceForeground)
{
foreach (var activeprocess in process.PkgList)
{
if (activeprocess == ctx.PackageName)
return false;
}
}
}
return true;
}
private void restoreApp()
{
Intent i = new Intent(ctx, typeof(MainActivity));
i.AddFlags(ActivityFlags.NewTask);
ctx.StartActivity(i);
}
public override IBinder OnBind(Intent intent)
{
return null;
}
}
}
I started this service in the OnCreate of MainActivity:
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
StartService(new Intent(this, typeof(KioskService)));
}

Query Firebase Value OnChange

I am trying to register a Xamarin app to listen for value changes on a particular value change. I can access the value, but for some reason when I listen to the value change, it will fire once but never again until I reboot.
I am using Xamarin Studio, and FireSharp libraries. This is the API code in the C# library portion of the app. The reason I have removed the delegate was to check if it wasn't the delegate being cleaned up after the first call or something.
public class ValueAPI
{
private IFirebaseClient _client;
private ITemperatureListener _listener;
private EventStreamResponse _response;
public ValueAPI()
{
IFirebaseConfig config = new FirebaseConfig
{
AuthSecret = "...",
BasePath = "https://[value-api].firebaseio.com/"
};
_client = new FirebaseClient(config);
}
public async Task<string> getValue()
{
FirebaseResponse response = await _client.GetAsync("VALUE");
return response.Body;
}
public async Task<string> registerForUpdates(IValueListener listener)
{
_listener = listener;
_response = await _client.OnAsync("VALUE", null, this.OnValueChange, null, null);
return _response.ToString();
}
private void OnValueChange(object sender, ValueChangedEventArgs args, object context)
{
if (_listener != null)
{
_listener.OnValueUpdated(args.Data);
}
}
}
ANDROID CODE
ValueAPI api = new ValueAPI();
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
getValue();
getValueUpdates();
}
private async void getValue()
{
Task<string> task = api.getValue();
string result = await task;
TextView label = (TextView)FindViewById(Resource.Id.label_value);
label.Text = result;
}
private async void getValueUpdates()
{
Task<string> task = api.registerForUpdates(this);
await task;
}
public void OnValueUpdated(string value)
{
TextView label = (TextView)FindViewById(Resource.Id.label_value_updated);
label.Text = value;
}

How to use SqlAzureExecutionStrategy and "Nolock"

To deal with SQL timeouts I'm trying to use SqlAzureExecutionStrategy (https://msdn.microsoft.com/en-us/data/dn456835.aspx)
The problem I am running into is it prevents "user initiated transactions" which seem to be the recommended way to implement "with (nolock)" in EF (http://www.hanselman.com/blog/GettingLINQToSQLAndLINQToEntitiesToUseNOLOCK.aspx, NOLOCK with Linq to SQL).
example code
public AspnetUser GetAspnetUserByUserName(string userName)
{
using (var tx = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = IsolationLevel.ReadUncommitted }))
{
return context.AspnetUsers.Where(x => x.UserName == userName).FirstOrDefault();
}
}
throws error
The configured execution strategy 'SqlAzureExecutionStrategy' does not support user initiated transactions. See http://go.microsoft.com/fwlink/?LinkId=309381 for additional information.
I've seen the answers that say to turn off the SqlAzureExecutionStrategy on a per call basis, but that would defeat the purpose of using it, if all my reads ignored the strategy. It is possible to have both "NoLock" and SqlAzureExecutionStrategy
SqlAzureExecutionStrategy doesn't support transactions initiated outside the action to be retried. To work around this restriction you would need to suspend the strategy, create the transaction scope and do the work as an action that you manually pass to the execution strategy to be retried:
public AspnetUser GetAspnetUserByUserName(string userName)
{
new SuspendableSqlAzureExecutionStrategy().Execute(() =>
{
using (var tx = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions() { IsolationLevel = IsolationLevel.ReadUncommitted }))
{
return context.AspnetUsers.Where(x => x.UserName == userName).FirstOrDefault();
}
});
}
Here I am using an alternative to the suspendable strategy from https://msdn.microsoft.com/en-us/data/dn307226 that will suspend any nested invocations automatically:
using System.Data.Entity.Infrastructure;
using System.Data.Entity.SqlServer;
using System.Data.Entity.Utilities;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using System.Threading.Tasks;
public class SuspendableSqlAzureExecutionStrategy : IDbExecutionStrategy
{
private readonly IDbExecutionStrategy _azureExecutionStrategy;
public SuspendableSqlAzureExecutionStrategy()
{
_azureExecutionStrategy = new SqlAzureExecutionStrategy();
}
private static bool Suspend
{
get { return (bool?)CallContext.LogicalGetData("SuspendExecutionStrategy") ?? false; }
set { CallContext.LogicalSetData("SuspendExecutionStrategy", value); }
}
public bool RetriesOnFailure
{
get { return !Suspend; }
}
public virtual void Execute(Action operation)
{
if (!RetriesOnFailure)
{
operation();
return;
}
try
{
Suspend = true;
_azureExecutionStrategy.Execute(operation);
}
finally
{
Suspend = false;
}
}
public virtual TResult Execute<TResult>(Func<TResult> operation)
{
if (!RetriesOnFailure)
{
return operation();
}
try
{
Suspend = true;
return _azureExecutionStrategy.Execute(operation);
}
finally
{
Suspend = false;
}
}
public virtual async Task ExecuteAsync(Func<Task> operation, CancellationToken cancellationToken)
{
if (!RetriesOnFailure)
{
await operation();
return;
}
try
{
Suspend = true;
await _azureExecutionStrategy.ExecuteAsync(operation, cancellationToken);
}
finally
{
Suspend = false;
}
}
public virtual async Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> operation, CancellationToken cancellationToken)
{
if (!RetriesOnFailure)
{
return await operation();
}
try
{
Suspend = true;
return await _azureExecutionStrategy.ExecuteAsync(operation, cancellationToken);
}
finally
{
Suspend = false;
}
}
}
public class MyConfiguration : DbConfiguration
{
public MyConfiguration()
{
SetExecutionStrategy("System.Data.SqlClient", () => new SuspendableSqlAzureExecutionStrategy());
}
}

Resources