How can I stop a Device.StartTimer () when I change pages?

I have a ListView that I am updating every 5 seconds using Device.StartTimer () and I would like to stop the timer when it leaves the ViewModel page. as you must intuit necsito do this because Device.StartTimer () is global and even when I change the page is still updating my ListView, how can I make ViewModel know that I'm changing pages?

This is part of my ViewModel:

private ObservableCollection sensors;

    public ObservableCollection<PcData> Sensors
    {
        get { return sensors; }
        set
        {
            sensors = value;
            OnPropertyChanged();
        }
    }


    public MonitoringTabsViewModel(string idCode, string description)
    {
        Description = description;
        LoadSensors(idCode);
        Device.StartTimer(TimeSpan.FromSeconds(5), () =>
        {
            RefreshSensors(idCode);
            return true;
        });
    }

    private async void LoadSensors(string idCode)
    {
        Sensors = new ObservableCollection<PcData>(await App.WebApiManager.GetCurrentStatusDeviceAsync(idCode));
    }

    private async void RefreshSensors(string idCode)
    {
        Sensors = null;
        Sensors = new ObservableCollection<PcData>(await App.WebApiManager.GetCurrentStatusDeviceAsync(idCode));
    }

Answers

  • stemadostemado Member ✭✭✭
    The Prism library for Xamarin Forms allows you to override OnNavigatedTo and OnNavigatedFrom methods within your ViewModel.

    You can then use them to start and stop the timer (respectively) when navigating to and navigating away from your view.

    https://www.nuget.org/packages/Prism.Unity.Forms/
  • lavilasolavilaso Member ✭✭

    @stemado said:
    The Prism library for Xamarin Forms allows you to override OnNavigatedTo and OnNavigatedFrom methods within your ViewModel.

    You can then use them to start and stop the timer (respectively) when navigating to and navigating away from your view.

    https://www.nuget.org/packages/Prism.Unity.Forms/

    Ok, but at this moment I would like to find some implementation without a framework, I have been researching and I try to do it using OnAppearing and OnDisAppearing and I'm messaging MessaingCenter, although I still do not get a satisfactory implementation.
    Any idea taking the above into consideration?

  • lavilasolavilaso Member ✭✭

    In the end I have come to the following implementation which actually does what I wanted:
    ViewModel:

    public class MonitoringTabsViewModel : Notificable
    {
        public string IdCode { get; set; }
    
        public bool InPage { get; set; }
    
        private string description;
    
        public string Description
        {
            get { return description; }
            set
            {
                description = value;
                OnPropertyChanged();
            }
        }
    
        private ObservableCollection<PcData> sensors;
    
        public ObservableCollection<PcData> Sensors
        {
            get { return sensors; }
            set
            {
                sensors = value;
                OnPropertyChanged();
            }
        }
    
    
        public MonitoringTabsViewModel(string idCode, string description)
        {
            IdCode = idCode;
            Description = description;
            LoadSensors(idCode);
            MessagingCenter.Subscribe<MonitoringView>(this, "OnAppearing", (sender) =>
            {
                InPage = true;
            });
            MessagingCenter.Subscribe<MonitoringView>(this, "OnDisAppearing", (sender) =>
            {
                InPage = false;
            });
            Device.StartTimer(TimeSpan.FromSeconds(5), TimerCallBack);
        }
    
        private bool TimerCallBack()
        {
            if (InPage)
            {
                RefreshSensors(IdCode);
                MessagingCenter.Unsubscribe<MonitoringView>(this, "OnAppearing");
                return true;
            }
            else
            {
                MessagingCenter.Unsubscribe<MonitoringView>(this, "OnDisAppearing");
                return false;
            }
        }
    
        private async void LoadSensors(string idCode)
        {
            Sensors = new ObservableCollection<PcData>(await App.WebApiManager.GetCurrentStatusDeviceAsync(idCode));
        }
    
        private async void RefreshSensors(string idCode)
        {
            Sensors = null;
            Sensors = new ObservableCollection<PcData>(await App.WebApiManager.GetCurrentStatusDeviceAsync(idCode));
        }
    

    View:

        protected override void OnAppearing()
        {
            base.OnAppearing();
            MessagingCenter.Send<MonitoringView>(this, "OnAppearing");
        }
    
        protected override void OnDisappearing()
        {
            base.OnDisappearing();
            MessagingCenter.Send<MonitoringView>(this, "OnDisAppearing");
        }
    

    There are still two things that concern me:
    1. I do not know if the management I'm giving to the MessagingCenter is appropriate, as you can see I'm unsubscribing in my TimerCallBack method, by putting breakpoints in the two calls to the unsubscribe method I see that while the timer is running every 5 seconds The unsubscribe method of the onAppearing message is still called.
    2. Although this implmentacion works, I still have the problem that when sleeping the application or put it in the background is still running my method RefreshSensors () and I would like to be in segudno flat also stop the execution.

    Could someone give me ideas of these two concerns that I still have?

  • LandLuLandLu Member, Xamarin Team Xamurai

    For your first question, I know MessagingCenter could do what you want, but call a method in your view model when page's OnAppearing fires will easily fit your request and more appropriate.
    ViewModel:

    public class MonitoringTabsViewModel : Notificable
    {
        public void PageOnAppearing()
        {
            // Do something
        }
        public void PageOnDisappearing()
        {
            // Do something
        }
    }
    

    Page:

    protected override void OnAppearing()
    {
        base.OnAppearing();
        ((MonitoringTabsViewModel)BindingContext).PageOnAppearing();
    }
    
    protected override void OnDisappearing()
    {
        base.OnDisappearing();
        ((MonitoringTabsViewModel)BindingContext).PageOnDisappearing();
    }
    

    In this way, there's no need for your view model to be aware of your page. Also there's no need to unsubscribe it.
    For your second question, you need to use MessagingCenter to achieve that.
    In the App.cs we could know the app's life cycle by this:

    protected override void OnSleep()
    {
        // Handle when your app sleeps
        // Send a Message here
        MessagingCenter.Send<object>(this, "AppOnSleep");
    }
    

    Then subscribe this messaging center in your view model's PageOnAppearing() and unsubscribe it in PageOnDisappearing();

    public void PageOnAppearing()
    {
        MessagingCenter.Subscribe<object>(this, "AppOnSleep", (sender) =>
        {
    
        });
    }
    public void PageOnDisappearing()
    {
        MessagingCenter.Unsubscribe<object>(this, "AppOnSleep");
    }
    
  • lavilasolavilaso Member ✭✭

    @LandLu said:
    For your first question, I know MessagingCenter could do what you want, but call a method in your view model when page's OnAppearing fires will easily fit your request and more appropriate.
    ViewModel:

    public class MonitoringTabsViewModel : Notificable
    {
        public void PageOnAppearing()
        {
            // Do something
        }
        public void PageOnDisappearing()
        {
            // Do something
        }
    }
    

    Page:

    protected override void OnAppearing()
    {
    base.OnAppearing();
    ((MonitoringTabsViewModel)BindingContext).PageOnAppearing();
    }

    protected override void OnDisappearing()
    {
    base.OnDisappearing();
    ((MonitoringTabsViewModel)BindingContext).PageOnDisappearing();
    }
    In this way, there's no need for your view model to be aware of your page. Also there's no need to unsubscribe it.
    For your second question, you need to use MessagingCenter to achieve that.
    In the App.cs we could know the app's life cycle by this:

    protected override void OnSleep()
    {
        // Handle when your app sleeps
        // Send a Message here
        MessagingCenter.Send<object>(this, "AppOnSleep");
    }
    

    Then subscribe this messaging center in your view model's PageOnAppearing() and unsubscribe it in PageOnDisappearing();

    public void PageOnAppearing()
    {
        MessagingCenter.Subscribe<object>(this, "AppOnSleep", (sender) =>
        {
    
        });
    }
    public void PageOnDisappearing()
    {
        MessagingCenter.Unsubscribe<object>(this, "AppOnSleep");
    }
    

    Thanks for answering.
    I believe that your first answer can not be implemented in the way it indicates because the BindingContext of my page is not MonitoringTabsViewModel, it is another class called MonitoringViewModel from which instances of MonitoringTabsViewmodel are made, all this because here what is built is a TabbedPage dynamic.
    The second answer is very related to the first one as I see it, so I think that I will have to rethink it to achieve what I want.
    Can you think of an alternative way to do that? or a form compatible with my infrastructure?
    View:

    public partial class MonitoringView : TabbedPage
    {
    
        MonitoringViewModel context = new MonitoringViewModel();
    
        public MonitoringView()
        {
            InitializeComponent();
            BindingContext = context;
        }
    
        protected override void OnAppearing()
        {
            base.OnAppearing();
            MessagingCenter.Send<MonitoringView>(this, "OnAppearing");
        }
    
        protected override void OnDisappearing()
        {
            base.OnDisappearing();
            MessagingCenter.Send<MonitoringView>(this, "OnDisAppearing");
        }
    }
    
Sign In or Register to comment.