Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Persisting navigation state #26

Open
gentledepp opened this issue Apr 29, 2024 · 3 comments
Open

Persisting navigation state #26

gentledepp opened this issue Apr 29, 2024 · 3 comments

Comments

@gentledepp
Copy link

AvaloniaUI has a tutorial (and even a docs page) on how to persist UI state with ReactiveUI

Is there any way we can actually persist the routing state of the Shell like this too?

How do you solve this in your projects? I'd be very interested to learn :-)

@OmidID
Copy link
Contributor

OmidID commented Apr 30, 2024

Hello @gentledepp
It is possible. I already did this in my project.
Basically the shell is very UI native and to handle the application architecture is up to the developer.

public class ModelBasePage<T> : Page
    where T : ViewModelBase
{
    public T ViewModel { get; set; }

    protected override Type StyleKeyOverride => typeof(Page);

    private CancellationTokenSource? _cancellationTokenSource;
    protected CancellationToken? PageLifetimeCancellationToken => _cancellationTokenSource?.Token;

    private void KillToken()
    {
        try { _cancellationTokenSource?.Cancel(); } catch { }
        try { _cancellationTokenSource?.Dispose(); } catch { }
    }

    protected override void OnDataContextChanged(EventArgs e)
    {
        if (DataContext is T viewModel)
        {
            ViewModel = viewModel;
            viewModel.LifetimeCancellationToken = PageLifetimeCancellationToken;
            ViewModel.Navigator = Navigator;
        }
        base.OnDataContextChanged(e);
    }
    
    public override async Task InitialiseAsync(CancellationToken cancellationToken)
    {
        KillToken();
        _cancellationTokenSource = new CancellationTokenSource();
        
        if (DataContext is T viewModel)
        {
            ViewModel = viewModel;
        }
        else
        {
            DataContext = ViewModel = Locator.Current.GetService<T>() ?? throw new KeyNotFoundException("Cannot find ViewModel");
        }
        ViewModel.LifetimeCancellationToken = PageLifetimeCancellationToken;
        ViewModel.Navigator = Navigator;

        await base.InitialiseAsync(_cancellationTokenSource.Token);
        await ViewModel.InitializeAsync(_cancellationTokenSource.Token);
    }

    public override Task ArgumentAsync(object args, CancellationToken cancellationToken)
    {
        if (DataContext is ViewModelBase viewModel)
            return viewModel.HandleParameter(args, _cancellationTokenSource?.Token ?? cancellationToken);

        return base.ArgumentAsync(args, cancellationToken);
    }

    public override Task TerminateAsync(CancellationToken cancellationToken)
    {
        KillToken();
        return base.TerminateAsync(cancellationToken);
    }
}

And this is the class for ViewModelBase:

public class ViewModelBase : ReactiveObject
{
    public enum ViewModelStatus
    {
        Initialize,
        Starting,
        Started,
        Closed
    }
    
    public INavigator? Navigator { get; set; }

    public object? Parameter { get; set; }

    [Reactive] public ViewModelStatus Status { get; private set; } = ViewModelStatus.Initialize;
    public CancellationToken? LifetimeCancellationToken { get; internal set; }

    public async Task InitializeAsync(CancellationToken cancellationToken)
    {
        if (Status != ViewModelStatus.Initialize) return;

        Status = ViewModelStatus.Starting;
        await StartAsync(cancellationToken);
        Status = ViewModelStatus.Started;
    }

    public Task HandleParameter(object parameter, CancellationToken cancellationToken)
    {
        Parameter = parameter;
        return ParameterAsync(parameter, cancellationToken);
    }

    protected virtual Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;

    protected virtual Task ParameterAsync(object parameter, CancellationToken cancellationToken) => Task.CompletedTask;

    protected T GetParameter<T>() => Parameter is T cast ? cast : default;
}

@gentledepp
Copy link
Author

gentledepp commented May 2, 2024

Thanks for your answer, but I think we are talking about different things.

I was asking for being able to persist navigation state.

So like:
in our app, we started in the Main page, then nagivated to Page2 and Page3.
Then the app is suspended.
When it is continued, I would like to load that navigation state back into the shell so the user can still navigate back to page 2 and the Main page, still having all ViewModels state persisted (like the search string or whatever)

So what I would need is:

  • a way to persist the navigation state of the shell as json or xml or whatever
  • and a way to load it back in

@OmidID
Copy link
Contributor

OmidID commented May 2, 2024

OK. What I understand you need to navigate from home page to page1 then page 2 and then back to home page with some argument.
Possible!
Please check the Navigate method from INavigator and you have navigate type and you can pass Clear type.
You can find list of nav types here: https://github.com/AvaloniaInside/Shell/blob/main/src/AvaloniaInside.Shell/NavigateType.cs

In case if you want to send value you can pass it as argument.

in this way, the instance of your main page will remain same.

The navigation stack keep the instances of your pages until it remove from stack. Very similar to mobile navigation stack.

Navigator.NavigateAsync("/main", NavigateType.Clear, "Value 1 selected", cancellationToken);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants