Навигация корпоративных приложений

Примечание.

Эта электронная книга была опубликована весной 2017 года и с тех пор не была обновлена. Есть много в книге, которая остается ценным, но некоторые из материалов устарели.

Xamarin.Forms включает поддержку навигации по страницам, которая обычно приводит к взаимодействию пользователя с пользовательским интерфейсом или из самого приложения в результате внутренних изменений состояния на основе логики. Однако навигация может быть сложной для реализации в приложениях, использующих шаблон Model-View-ViewModel (MVVM), так как необходимо выполнить следующие задачи:

  • Как определить представление для перехода, используя подход, который не представляет жесткой связи и зависимостей между представлениями.
  • Как координировать процесс перехода к представлению инициализировать инициализировать его. При использовании MVVM модель представления и представления должна быть создана и связана друг с другом с помощью контекста привязки представления. Если приложение использует контейнер внедрения зависимостей, экземпляр представлений и моделей представлений может потребовать определенного механизма построения.
  • Независимо от того, следует ли выполнять навигацию в режиме представления или просматривать модель в первую очередь. При переходе по представлению на страницу для перехода к имени типа представления. Во время навигации создается экземпляр указанного представления вместе с соответствующей моделью представления и другими зависимыми службами. Альтернативный подход — использовать навигацию модели представления, где страница для перехода ссылается на имя типа модели представления.
  • Как четко разделить поведение навигации приложения по представлениям и моделям просмотра. Шаблон MVVM обеспечивает разделение пользовательского интерфейса приложения и его презентации и бизнес-логики. Однако поведение навигации приложения часто охватывает элементы пользовательского интерфейса и презентаций приложения. Пользователь часто инициирует навигацию из представления, и представление будет заменено в результате навигации. Однако также может потребоваться инициировать или координировать навигацию из модели представления.
  • Передача параметров во время навигации для целей инициализации. Например, если пользователь переходит к представлению для обновления сведений о заказе, данные заказа должны передаваться в представление, чтобы он смог отобразить правильные данные.
  • Как координировать навигацию, чтобы обеспечить соблюдение определенных бизнес-правил. Например, пользователям может быть предложено перейти к представлению, чтобы они могли исправить любые недопустимые данные или отправить или отсоединить карта любые изменения данных, внесенные в представление.

В этой главе рассматриваются эти проблемы путем представления NavigationService класса, который используется для навигации по модели первой страницы.

Примечание.

Используется NavigationService приложением только для выполнения иерархической навигации между экземплярами ContentPage. Использование службы для перехода между другими типами страниц может привести к неожиданному поведению.

Логика навигации может находиться в коде представления или в модели представления с привязкой к данным. Хотя логика навигации в представлении может быть самым простым подходом, это не легко тестировать с помощью модульных тестов. Размещение логики навигации в классах модели представления означает, что логика может выполняться с помощью модульных тестов. Кроме того, модель представления может реализовать логику для управления навигацией, чтобы обеспечить применение определенных бизнес-правил. Например, приложение может не позволить пользователю перейти с страницы, не убедившись, что введенные данные действительны.

NavigationService Класс обычно вызывается из моделей представлений для повышения тестируемости. Однако для перехода к представлениям из моделей представлений потребуется, чтобы модели представления ссылались на представления, и особенно представления, с которыми не связана активная модель представления, с которой не рекомендуется. Таким образом, представленный NavigationService здесь указывает тип модели представления в качестве целевого объекта для перехода.

Мобильное приложение eShopOnContainers использует NavigationService класс для предоставления навигации по модели представления. Этот класс реализует INavigationService интерфейс, который показан в следующем примере кода:

public interface INavigationService  
{  
    ViewModelBase PreviousPageViewModel { get; }  
    Task InitializeAsync();  
    Task NavigateToAsync<TViewModel>() where TViewModel : ViewModelBase;  
    Task NavigateToAsync<TViewModel>(object parameter) where TViewModel : ViewModelBase;  
    Task RemoveLastFromBackStackAsync();  
    Task RemoveBackStackAsync();  
}

Этот интерфейс указывает, что класс реализации должен предоставлять следующие методы:

Способ Характер использования
InitializeAsync Выполняет навигацию на одну из двух страниц при запуске приложения.
NavigateToAsync Выполняет иерархическую навигацию на указанную страницу.
NavigateToAsync(parameter) Выполняет иерархическую навигацию на указанную страницу, передавая параметр.
RemoveLastFromBackStackAsync Удаляет предыдущую страницу из стека навигации.
RemoveBackStackAsync Удаляет все предыдущие страницы из стека навигации.

Кроме того, интерфейс указывает, INavigationService что реализующий класс должен предоставить PreviousPageViewModel свойство. Это свойство возвращает тип модели представления, связанный с предыдущей страницей в стеке навигации.

Примечание.

Интерфейс INavigationService обычно также указывает GoBackAsync метод, который используется для программного возврата на предыдущую страницу в стеке навигации. Однако этот метод отсутствует в мобильном приложении eShopOnContainers, так как он не требуется.

Создание экземпляра NavigationService

Класс NavigationService , реализующий INavigationService интерфейс, регистрируется как однотонный с контейнером внедрения зависимостей Autofac, как показано в следующем примере кода:

builder.RegisterType<NavigationService>().As<INavigationService>().SingleInstance();

Интерфейс INavigationService разрешается в конструкторе классов, как показано в ViewModelBase следующем примере кода:

NavigationService = ViewModelLocator.Resolve<INavigationService>();

Возвращает ссылку на NavigationService объект, хранящийся в контейнере внедрения зависимостей Autofac, который создается InitNavigation методом в App классе. Дополнительные сведения см. в разделе "Навигация при запуске приложения".

Класс ViewModelBase сохраняет NavigationService экземпляр в свойстве NavigationService типа INavigationService. Поэтому все классы модели представления, производные от ViewModelBase класса, могут использовать NavigationService свойство для доступа к методам, указанным интерфейсом INavigationService . Это позволяет избежать затрат на внедрение NavigationService объекта из контейнера внедрения зависимостей Autofac в каждый класс модели представления.

Обработка запросов навигации

Xamarin.FormsNavigationPage предоставляет класс, который реализует иерархическую навигацию, в которой пользователь может перемещаться по страницам, вперед и назад, по мере необходимости. Дополнительные сведения об иерархической навигации см. в статье Иерархическая навигация в .

Вместо прямого использования NavigationPage класса приложение eShopOnContainers упаковывает NavigationPage класс в класс, как показано в CustomNavigationView следующем примере кода:

public partial class CustomNavigationView : NavigationPage  
{  
    public CustomNavigationView() : base()  
    {  
        InitializeComponent();  
    }  

    public CustomNavigationView(Page root) : base(root)  
    {  
        InitializeComponent();  
    }  
}

Эта оболочка предназначена для упрощения стилизации NavigationPage экземпляра внутри XAML-файла для класса.

Навигация выполняется в классах модели представления, вызывая один из NavigateToAsync методов, указывая тип модели представления для перемещаемой страницы, как показано в следующем примере кода:

await NavigationService.NavigateToAsync<MainViewModel>();

В следующем примере кода показаны NavigateToAsync методы, предоставляемые классом NavigationService :

public Task NavigateToAsync<TViewModel>() where TViewModel : ViewModelBase  
{  
    return InternalNavigateToAsync(typeof(TViewModel), null);  
}  

public Task NavigateToAsync<TViewModel>(object parameter) where TViewModel : ViewModelBase  
{  
    return InternalNavigateToAsync(typeof(TViewModel), parameter);  
}

Каждый метод позволяет любому классу модели представления, производным от ViewModelBase класса, выполнять иерархическую навигацию путем вызова InternalNavigateToAsync метода. Кроме того, второй NavigateToAsync метод позволяет указывать данные навигации в качестве аргумента, передаваемого в модель представления, к которой он обычно используется для инициализации. Дополнительные сведения см. в разделе "Передача параметров во время навигации".

Метод InternalNavigateToAsync выполняет запрос навигации и показан в следующем примере кода:

private async Task InternalNavigateToAsync(Type viewModelType, object parameter)  
{  
    Page page = CreatePage(viewModelType, parameter);  

    if (page is LoginView)  
    {  
        Application.Current.MainPage = new CustomNavigationView(page);  
    }  
    else  
    {  
        var navigationPage = Application.Current.MainPage as CustomNavigationView;  
        if (navigationPage != null)  
        {  
            await navigationPage.PushAsync(page);  
        }  
        else  
        {  
            Application.Current.MainPage = new CustomNavigationView(page);  
        }  
    }  

    await (page.BindingContext as ViewModelBase).InitializeAsync(parameter);  
}  

private Type GetPageTypeForViewModel(Type viewModelType)  
{  
    var viewName = viewModelType.FullName.Replace("Model", string.Empty);  
    var viewModelAssemblyName = viewModelType.GetTypeInfo().Assembly.FullName;  
    var viewAssemblyName = string.Format(  
                CultureInfo.InvariantCulture, "{0}, {1}", viewName, viewModelAssemblyName);  
    var viewType = Type.GetType(viewAssemblyName);  
    return viewType;  
}  

private Page CreatePage(Type viewModelType, object parameter)  
{  
    Type pageType = GetPageTypeForViewModel(viewModelType);  
    if (pageType == null)  
    {  
        throw new Exception($"Cannot locate page type for {viewModelType}");  
    }  

    Page page = Activator.CreateInstance(pageType) as Page;  
    return page;  
}

Метод InternalNavigateToAsync выполняет навигацию по модели представления, сначала вызывая CreatePage метод. Этот метод находит представление, соответствующее указанному типу модели представления, и создает и возвращает экземпляр этого типа представления. При поиске представления, соответствующего типу модели представления, используется подход на основе соглашения, который предполагает следующее:

  • Представления находятся в той же сборке, что и типы моделей представления.
  • Представления находятся в . Просматривает дочернее пространство имен.
  • Модели просмотра находятся в . Представление дочернего пространства имен ViewModels.
  • Имена представлений соответствуют именам моделей представления, а "Модель" удалена.

При создании экземпляра представления он связан с соответствующей моделью представления. Дополнительные сведения о том, как это происходит, см. в статье автоматическое создание модели представления с помощью указателя модели представления.

Если созданное представление представляет собой представление LoginView, оно упаковывается в новый экземпляр CustomNavigationView класса и назначается свойству Application.Current.MainPage . В противном случае экземпляр извлекается и указывается, CustomNavigationView что он не имеет значения NULL, PushAsync метод вызывается для отправки представления, созданного в стек навигации. Тем не менее, если извлеченный CustomNavigationView экземпляр nullявляется, создаваемое представление упаковывается в новый экземпляр CustomNavigationView класса и назначается свойству Application.Current.MainPage . Этот механизм гарантирует, что во время навигации страницы добавляются правильно в стек навигации как при пустом, так и в том случае, если он содержит данные.

Совет

Рассмотрите возможность кэширования страниц. Кэширование страниц приводит к потреблению памяти для представлений, которые в настоящее время не отображаются. Однако без кэширования страниц это означает, что анализ и построение страницы XAML и его модель представления будут происходить каждый раз при переходе на новую страницу, что может повлиять на производительность сложной страницы. Для хорошо разработанной страницы, которая не использует чрезмерное количество элементов управления, производительность должна быть достаточной. Однако кэширование страниц может помочь при обнаружении медленного времени загрузки страницы.

После создания представления и перехода InitializeAsync к ней выполняется метод связанной модели представления. Дополнительные сведения см. в разделе "Передача параметров во время навигации".

При запуске InitNavigation приложения вызывается метод в App классе. Этот метод показан в следующем примере кода:

private Task InitNavigation()  
{  
    var navigationService = ViewModelLocator.Resolve<INavigationService>();  
    return navigationService.InitializeAsync();  
}

Метод создает новый NavigationService объект в контейнере внедрения зависимостей Autofac и возвращает ссылку на него перед вызовом метода InitializeAsync .

Примечание.

INavigationService Когда интерфейс разрешается классомViewModelBase, контейнер возвращает ссылку на NavigationService объект, созданный при вызове метода InitNavigation.

Метод NavigationServiceInitializeAsync показан в следующем примере кода:

public Task InitializeAsync()  
{  
    if (string.IsNullOrEmpty(Settings.AuthAccessToken))  
        return NavigateToAsync<LoginViewModel>();  
    else  
        return NavigateToAsync<MainViewModel>();  
}

Выполняется MainView переход к приложению, если у приложения есть кэшированный маркер доступа, который используется для проверки подлинности. LoginView В противном случае перейдите к ней.

Дополнительные сведения о контейнере внедрения зависимостей autofac см. в разделе "Введение в внедрение зависимостей".

Передача параметров во время навигации

Один из NavigateToAsync методов, указанных INavigationService интерфейсом, позволяет указывать данные навигации в качестве аргумента, передаваемого в модель представления, к которой он обычно используется для инициализации.

Например, класс содержит объектOrderDetailCommand, ProfileViewModel который выполняется при выборе заказа на ProfileView странице. В свою очередь это выполняет OrderDetailAsync метод, который показан в следующем примере кода:

private async Task OrderDetailAsync(Order order)  
{  
    await NavigationService.NavigateToAsync<OrderDetailViewModel>(order);  
}

Этот метод вызывает навигацию к OrderDetailViewModelэкземпляру Order , который представляет порядок, выбранный пользователем ProfileView на странице. NavigationService При создании OrderDetailViewOrderDetailViewModel класса создается экземпляр класса и назначается представлениюBindingContext. После перехода к OrderDetailViewInternalNavigateToAsync методу метод выполняет InitializeAsync метод связанной модели представления.

Метод InitializeAsync определяется в классе как ViewModelBase метод, который можно переопределить. Этот метод задает object аргумент, представляющий данные, передаваемые в модель представления во время операции навигации. Поэтому классы модели представления, которые хотят получать данные из операции навигации, предоставляют собственную реализацию метода для выполнения требуемой InitializeAsync инициализации. В следующем примере кода показан InitializeAsync метод из OrderDetailViewModel класса:

public override async Task InitializeAsync(object navigationData)  
{  
    if (navigationData is Order)  
    {  
        ...  
        Order = await _ordersService.GetOrderAsync(  
                        Convert.ToInt32(order.OrderNumber), authToken);  
        ...  
    }  
}

Этот метод извлекает Order экземпляр, переданный в модель представления во время операции навигации, и использует его для получения сведений о полном порядке из экземпляра OrderService .

Вызов навигации с помощью поведения

Навигация обычно активируется из представления взаимодействием пользователя. Например, LoginView выполняет навигацию после успешной проверки подлинности. В следующем примере кода показано, как навигация вызывается поведением:

<WebView ...>  
    <WebView.Behaviors>  
        <behaviors:EventToCommandBehavior  
            EventName="Navigating"  
            EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"  
            Command="{Binding NavigateCommand}" />  
    </WebView.Behaviors>  
</WebView>

Во время выполнения EventToCommandBehavior ответит на взаимодействие с WebViewним. WebView При переходе на веб-страницу Navigating событие запустится, которое будет выполняться NavigateCommand в .LoginViewModel По умолчанию аргументы событий для события передаются команде. Эти данные преобразуются при передаче между источником и целевым преобразователем, указанным в EventArgsConverter свойстве, который возвращает Url из WebNavigatingEventArgsнего. Поэтому при NavigationCommand выполнении URL-адрес веб-страницы передается в качестве параметра зарегистрированной Action.

В свою очередь, метод NavigationCommand выполняется NavigateAsync , который показан в следующем примере кода:

private async Task NavigateAsync(string url)  
{  
    ...          
    await NavigationService.NavigateToAsync<MainViewModel>();  
    await NavigationService.RemoveLastFromBackStackAsync();  
    ...  
}

Этот метод вызывает навигацию MainViewModelк навигации и следующую навигацию удаляет LoginView страницу из стека навигации.

Подтверждение или отмена навигации

Приложению может потребоваться взаимодействовать с пользователем во время операции навигации, чтобы пользователь мог подтвердить или отменить навигацию. Это может потребоваться, например, когда пользователь пытается перейти, прежде чем полностью завершить страницу записи данных. В этом случае приложение должно предоставить уведомление, позволяющее пользователю перейти с страницы или отменить операцию навигации до ее возникновения. Это можно сделать в классе модели представления с помощью ответа от уведомления, чтобы контролировать, вызывается ли навигация.

Итоги

Xamarin.Forms включает поддержку навигации по страницам, которая обычно приводит к взаимодействию пользователя с пользовательским интерфейсом или из самого приложения в результате изменений состояния на основе внутренней логики. Однако навигация может быть сложной для реализации в приложениях, использующих шаблон MVVM.

В этой главе представлен NavigationService класс, который используется для навигации модели представления из моделей представления. Размещение логики навигации в классах модели представления означает, что логика может выполняться с помощью автоматизированных тестов. Кроме того, модель представления может реализовать логику для управления навигацией, чтобы обеспечить применение определенных бизнес-правил.