Control View Model Lifetime

Choose your preferred method of configuring application navigation. Some options include:

If you choose Shell Navigation, then you'll need some help knowing when to load and unload view models. While Shell Navigation has the advantage of being built into .NET MAUI, it has significant shortcomings with respect to view model lifetime.

ILifecycleManaged

Jinaga.Maui provides support for controlling the lifetime of view models when using Shell Navigation. Each page-level view model should implement the ILifecycleManaged interface.

public partial class PostListViewModel : ObservableObject, IQueryAttributable, ILifecycleManaged
{
    private readonly JinagaClient jinagaClient;

    private Start? start;
    private IObserver? observer;

    public PostListViewModel(JinagaClient jinagaClient)
    {
        this.jinagaClient = jinagaClient;
    }

    public void ApplyQueryAttributes(IDictionary<string, object> query)
    {
        start = query["start"] as Start;
    }

    public void Load()
    {
        var specification = Given<Start>.Match((start, facts) =>
            //...
        );

        observer = jinagaClient.Watch(specification, start, projection =>
        {
            // Update the view model
        });
    }

    public void Unload()
    {
        observer?.Stop();
        observer = null;
    }
}

AddJinagaNavigation

Then add Jinaga navigation to the builder. Describe a tree of view model types that correspond to the navigation structure of the application.

builder.Services.AddJinagaNavigation(t => t
    .Add<RootViewModel>(t => t
        .Add<TabAViewModel>(t => t
            .Add<AChildViewModel>()
        )
        .Add<TabBViewModel>(t => t
            .Add<BChildViewModel>()
        )
    )
);

Capture Visibility

Finally, in each page, inform the navigation lifecycle manager when the view model is visible and hidden. Use the OnAppearing and OnNavigatedFrom events, as they occur before animating a new page in, and after animating an old page out. This ensures that the data remains visible on the page during the animation.

public partial class PostListPage : ContentPage
{
    private readonly PostListViewModel viewModel;
    private readonly INavigationLifecycleManager navigationLifecycleManager;

    public PostListPage(PostListViewModel viewModel, INavigationLifecycleManager navigationLifecycleManager)
    {
        InitializeComponent();
        BindingContext = viewModel;
        this.viewModel = viewModel;
        this.navigationLifecycleManager = navigationLifecycleManager;
    }

    protected override void OnAppearing()
    {
        navigationLifecycleManager.Visible(viewModel);
        base.OnAppearing();
    }

    protected override void OnNavigatedFrom(NavigatedFromEventArgs args)
    {
        navigationLifecycleManager.Hidden(viewModel);
        base.OnNavigatedFrom(args);
    }
}

The navigation lifecycle manager keeps view models higher up in the navigation tree loaded. This means that the data, selection, and scroll state will still be available when the user navigates back to the page.

UnloadInvisible

The navigation lifecycle manager keeps hidden view models loaded when the user is likely to navigate back to them. Ordinarily, this is the expected behavior, as it provides a better user experience. However, if the hidden view models contains information that the user should no longer be able to see, then they should be unloaded.

Call the UnloadInvisible method on the navigation lifecycle manager when the user logs out. This prevents the user from navigating back to the previous user's data.

public class AppShellViewModel
{
    public async Task HandleLogOut()
    {
        await authenticationService.LogOut();
        navigationLifecycleManager.UnloadInvisible();

        // Use two slashes to prevent back navigation.
        await Shell.Current.GoToAsync("//notloggedin");
    }
}

Imagine that the navigation structure is based on tabs. On one tab, the user selects one of their private conversations and opens a message. On the other tab, the user can see their profile and log out.

builder.Services.AddJinagaNavigation(t => t
    .Add<GatekeeperViewModel>(t => t
        .Add<ConversationListViewModel>(t => t
            .Add<MessageListViewModel>(t => t
                .Add<MessageViewModel>()
            )
        )
        .Add<UserProfileViewModel>()
    )
);

If the user was looking at a private message when they switched to the user profile tab, the message view model will still be loaded. The user might navigate back to the message, which they would expect to still be visible. Furthermore, the parent views of the conversation list and message list should preserve their scroll position and selection.

However, if the user logs out, then the message view model should be unloaded. The list of messages and conversations should similarly be unloaded. This prevents the next user from seeing the previous user's data.

Continue With

Load User Data

Jinaga is a product of Jinaga LLC.

Michael L Perry, President