Search code examples
data-bindingxamarin.formstabbedpage

Xamarin Forms: maintain selected tab over activity restarts


My main page uses a TabbedPage to group existing news into different lists. The tabs aren't fixed; they're built from a data binding operations against a collection that's retried through a web service call.

I'd like to persist the selected tab across activity restarts, but it seems like I'm missing something. Since there's no selected tab property (which can be set through data binding), I've tried to handle the PageChanged and the CurrentPageChangedCommand events. I'm using the PageChanged to set the selected tab to the previous selected tab and the CurrentPageChangedCommand is being used to update the persisted selected tab (I'm using the Application.Properties to make sure the selected tab survives app restarts).

Unfortunately, the events generated by the tab will always set tab 0 as the selected tab! Here's what I'm seeing (let's assume that my app was killed white tab 3 was active):

  1. When data is bound to the TabbedPage.ItemsSource property, the tab will automatically fire the CurrentPageChangedCommand, passing the first tab (tab at position 0).
  2. My code handles the event and updates the current persisted selected tab by changing the selected tab in the Properties dictionary. So now, instead of 3 (which was the value persisted when my app was killed), it will have 0.
  3. Then the tab will fire the PagesChanged
  4. When my code handles this event, it will try to update the selected tab. However, when it access the selected tab from the Properties dictionary, it will get the default tab (0) and not 3. This happens because the CurrentPageChangedCommand was fired before the PagesChanged event (step 2), completely overriding the previously persisted tab index.

This default behaviour will also give a bad user experience when the user refreshes the current list (pull to refresh) because he always ends up seeing tab 0 list.

So, any clues on how to solve this? How have you guys solved this?

Thanks.


Solution

  • It seems it can't be achieved using MVVM as CurrentPage is not a bindable property and CurrentPageChanged is an event.

    However, there's no need to handle the PagesChanged event. You could record the index in the changed event like:

    private void MyTabbedPage_CurrentPageChanged(object sender, EventArgs e)
    {
        Application.Current.Properties["index"] = this.Children.IndexOf(CurrentPage);
        Application.Current.SavePropertiesAsync();
    }
    

    Then you could set your tabbed page's current page after you have loaded all the tabs:

    object index;
    Application.Current.Properties.TryGetValue("index", out index);
    if (index != null)
    {
        CurrentPage = Children[int.Parse(index.ToString())];
    }
    // Subscribe the event
    CurrentPageChanged += MyTabbedPage_CurrentPageChanged;
    

    I placed the code above in the custom tabbed page's constructor and it could change the selected tab at initial time.

    Update:

    If you want to change the tabbed page's children dynamically, you could define a property to avoid the event being fired when you change the children:

    bool shouldChangeIndex = true;
    
    private void MyTabbedPage_CurrentPageChanged(object sender, EventArgs e)
    {
        if (shouldChangeIndex)
        {
            var index = this.Children.IndexOf(CurrentPage);
            Application.Current.Properties["index"] = index;
            Application.Current.SavePropertiesAsync();
        }            
    }
    
    // Simulate the adjusting
    shouldChangeIndex = false;
    Children.Clear();           
    Children.Add(new MainPage());
    Children.Add(new SecondPage());
    shouldChangeIndex = true;
    
    object index;
    Application.Current.Properties.TryGetValue("index", out index);
    if (index != null)
    {
        CurrentPage = Children[int.Parse(index.ToString())];
    }