Persisting navigation state
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 :-)
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;
}
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
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);