Навигация корпоративных приложений
Примечание.
Эта электронная книга была опубликована весной 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.
Метод NavigationService
InitializeAsync
показан в следующем примере кода:
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
При создании OrderDetailView
OrderDetailViewModel
класса создается экземпляр класса и назначается представлениюBindingContext
. После перехода к OrderDetailView
InternalNavigateToAsync
методу метод выполняет 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
класс, который используется для навигации модели представления из моделей представления. Размещение логики навигации в классах модели представления означает, что логика может выполняться с помощью автоматизированных тестов. Кроме того, модель представления может реализовать логику для управления навигацией, чтобы обеспечить применение определенных бизнес-правил.