Xamarin application not navigating back

Viewed 71

Problem

This is the process:

  1. Select a category from list.

  2. Load Tasks page.

  3. Tasks are loaded depending on the categoryId selected from the previous page. (Navigate back to Category page is possible ✔️)

  4. Select a Task from from list.

  5. Load Task Page.

  6. Task details are loaded on the page. (Navigate back to Tasks page is not possible ❌)

Video

enter image description here

Question

I do not understand why I cannot navigate back a page. How can I fix this?

Code

CategoriesViewModel

public class CategoriesViewModel : BaseViewModel
{
    public ObservableCollection<CategoryModel> Categories { get; } = new ObservableCollection<CategoryModel>();

    public Command LoadCategoriesCommand { get; }
    public Command<CategoryModel> SelectedCategory { get; }

    public CategoriesViewModel()
    {
        Title = "Categories";

        LoadCategoriesCommand = new Command(async () => await LoadCategories());
        SelectedCategory = new Command<CategoryModel>(OnSelectedCategory);
    }

    private async Task LoadCategories()
    {
        IsBusy = true;
        try
        {
            Categories.Clear();

            var categories = await DatabaseService.GetCategoriesAsync();
            foreach (var category in categories)
            {
                this.Categories.Add(category);
            }
        }
        catch (Exception)
        {

            throw;
        }
        finally
        {
            IsBusy = false;
        }
    }

    private async void OnSelectedCategory(CategoryModel category)
    {
        if (category == null)
            return;

        await Shell.Current.GoToAsync($"{nameof(TasksPage)}?{nameof(TasksViewModel.CategoryId)}={category.CategoryId}");
    }

    public void OnAppearing()
    {
        IsBusy = true;
    }
}

TasksViewModel

[QueryProperty(nameof(CategoryId), nameof(CategoryId))]
public class TasksViewModel : BaseViewModel
{
    public ObservableCollection<TaskModel> Tasks { get; } = new ObservableCollection<TaskModel>();

    private int categoryId;
    public int CategoryId
    {
        get { return categoryId; }
        set
        {
            categoryId = value;
        }
    }

    public Command LoadTasksCommand { get; set; }
    public Command<TaskModel> SelectedTask { get; set; }

    public TasksViewModel()
    {
        Title = "Tasks";
        LoadTasksCommand = new Command(async () => await LoadTasks());
        SelectedTask = new Command<TaskModel>(OnSelectedTask);
    }

    private async Task LoadTasks()
    {
        IsBusy = true;
        try
        {
            this.Tasks.Clear();

            var tasks = await DatabaseService.GetTasksAsync(CategoryId);
            foreach (var task in tasks)
            {
                this.Tasks.Add(task);
            }
        }
        catch (Exception)
        {

            throw;
        }
        finally
        {
            IsBusy = false;
        }
    }

    private async void OnSelectedTask(TaskModel task)
    {
        if (task == null)
            return;

        await Shell.Current.GoToAsync($"{nameof(TaskPage)}?{nameof(TaskViewModel.TaskId)}={task.TaskId}");
    }

    public void OnAppearing()
    {
        IsBusy = true;
    }
}

TaskViewModel

[QueryProperty(nameof(TaskId), nameof(TaskId))]
public class TaskViewModel : BaseViewModel
{
    private int taskId;

    public int TaskId
    {
        get { return taskId; }
        set
        {
            taskId = value;
        }
    }

    private string taskTitle;

    public string TaskTitle
    {
        get { return taskTitle; }
        set
        {
            taskTitle = value;
            OnPropertyChanged(nameof(TaskTitle));
        }
    }

    private string description;

    public string Description
    {
        get { return description; }
        set
        {
            description = value;
            OnPropertyChanged(nameof(Description));
        }
    }

    public Command LoadTaskCommand { get; }

    public TaskViewModel()
    {
        LoadTaskCommand = new Command(async () => await LoadTask());
    }

    private async Task LoadTask()
    {
        IsBusy = true;
        try
        {
            var task = await DatabaseService.GetTaskAsync(TaskId);
            this.TaskTitle = task.Title;
            this.Description = task.Description;
        }
        catch (Exception)
        {

            throw;
        }
        finally
        {
            IsBusy = false;
        }
    }

    public void OnAppearing()
    {
        IsBusy = true;
    }
}

Update 1

I tried replacing this line of code in TasksViewModel:

await Shell.Current.GoToAsync($"{nameof(TaskPage)}?{nameof(TaskViewModel.TaskId)}={task.TaskId}");

to this:

await Shell.Current.Navigation.PushAsync(new AboutPage());

Also, the same outcome.

Update 2

As per requested comment, here is the TaskPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:SomeProject.ViewModels"
             x:Class="SomeProject.Views.Task.TaskPage"
             Title="{Binding TaskTitle}">
    <ContentPage.Content>
        <RefreshView x:DataType="vm:TaskViewModel"
                     Command="{Binding LoadTaskCommand}"
                     IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
            <StackLayout>
                <Label Text="{Binding Description}" />
            </StackLayout>
        </RefreshView>
    </ContentPage.Content>
</ContentPage>

and TaskPage.xaml.cs:

public partial class TaskPage : ContentPage
{
    TaskViewModel _viewModel;
    public TaskPage()
    {
        InitializeComponent();
        BindingContext = _viewModel = new TaskViewModel();
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();
        _viewModel.OnAppearing();
    }
}

Update 3

As per requested comment, here is the routes:

public AppShell()
{
    InitializeComponent();
    Routing.RegisterRoute(nameof(CategoriesView), typeof(CategoriesView));
    Routing.RegisterRoute(nameof(TasksPage), typeof(TasksPage));
    Routing.RegisterRoute(nameof(TaskPage), typeof(TaskPage));
}
2 Answers

I had CategoryPage registered in AppShell.xaml.cs and also AppShell.xaml like so:

<ShellContent Route="CategoryPage" ContentTemplate="{DataTemplate local:CategoryPage}" />

Only can register one route in one or the other.

Related