Trying to implement a login page for Avalonia with an app using ReactiveUI, and for some reason after logging in, the router await HostScreen.Router.Navigate.Execute(App.Services.GetRequiredService<MainPageViewModel>()).ToTask();
just succeeded without changing the screen (view constructor is not called, but the view model is). The login result is cached, and if you close the application and start it again, the data is restored and OnAppearingAsync
successfully opens MainPage
.
What could be the problem?
Startup:
public partial class App : Application
{
public static IServiceProvider Services = null!; // Initialized before use
static App()
{
Akavache.Registrations.Start("App");
var services = new ServiceCollection();
services.UseMicrosoftDependencyResolver();
var resolver = Locator.CurrentMutable;
resolver.InitializeSplat();
resolver.InitializeReactiveUI();
ConfigureServices(services);
Services = services.BuildServiceProvider();
Services.UseMicrosoftDependencyResolver();
}
.....
private static IServiceCollection ConfigureServices(IServiceCollection services)
{
....
services.AddSingleton<MainWindowViewModel>();
services.AddSingleton<IScreen, MainWindowViewModel>();
services.AddSingleton<IViewFor<MainWindowViewModel>, MainWindow>();
services.AddTransient<IViewFor<LoginViewModel>, Login>();
services.AddTransient<LoginViewModel>();
services.AddTransient<IViewFor<MainPageViewModel>, MainPage>();
services.AddTransient<MainPageViewModel>();
return services;
}
}
MainWindows.xaml
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
....
<rxui:RoutedViewHost Router="{Binding Router}">
</rxui:RoutedViewHost>
</Window>
MainVM:
public class MainWindowViewModel : ReactiveObject, IScreen, IActivatableView
{
public MainWindowViewModel(AuthenticationManager authenticationManager)
{
AuthenticationManager = authenticationManager;
}
public RoutingState Router { get; } = new RoutingState();
public ViewModelActivator Activator { get; } = new ViewModelActivator();
public AuthenticationManager AuthenticationManager { get; }
public async Task OnAppearingAsync()
{
if (AuthenticationManager.IsAuthenticated)
{
await Router.Navigate.Execute(App.Services.GetRequiredService<MainPageViewModel>()).ToTask();
}
else
{
await Router.Navigate.Execute(App.Services.GetRequiredService<LoginViewModel>()).ToTask();
}
}
}
LoginVM:
public class LoginViewModel : ReactiveObject, IRoutableViewModel, IActivatableViewModel
{
private readonly ISchedulerProvider schedulerProvider;
public LoginViewModel(IScreen hostScreen, ISchedulerProvider schedulerProvider, AuthenticationManager authenticationManager)
{
HostScreen = hostScreen;
this.schedulerProvider = schedulerProvider;
AuthenticationManager = authenticationManager;
Login = ReactiveCommand.CreateFromTask(LoginAsync, outputScheduler: schedulerProvider.MainThread);
}
public string? UrlPathSegment { get; } = nameof(Views.Login);
public IScreen HostScreen { get; }
public ViewModelActivator Activator { get; } = new ViewModelActivator();
public ReactiveCommand<Unit, Unit> Login { get; }
private AuthenticationManager AuthenticationManager { get; }
private async Task LoginAsync()
{
//ToDo: lock button
try
{
if (!AuthenticationManager.IsAuthenticated)
{
await AuthenticationManager.LoginAsync();
}
await HostScreen.Router.Navigate.Execute(App.Services.GetRequiredService<MainPageViewModel>()).ToTask();
}
catch (Exception ex)
{
throw;
}
}
}
Thus, the problem was caused by DI. IScreen instance (MainVM) was registered several times with different type, so it was created twice and LogivVM accepted the second version
services.AddSingleton<MainWindowViewModel>();
services.AddSingleton<IScreen, MainWindowViewModel>();
Change to
services.AddSingleton<MainWindowViewModel>();
services.AddSingleton<IScreen, MainWindowViewModel>(sp => sp.GetRequiredService<MainWindowViewModel>());