엔터프라이즈 앱 탐색
참고 항목
이 전자책은 2017년 봄에 게시되었으며 그 이후로 업데이트되지 않았습니다. 이 책에는 귀중한 것들이 많이 있지만 일부 자료는 구식입니다.
Xamarin.Forms 에는 일반적으로 사용자가 UI와 상호 작용하거나 내부 논리 기반 상태 변경의 결과로 앱 자체에서 발생하는 페이지 탐색에 대한 지원이 포함됩니다. 그러나 MVVM(Model-View-ViewModel) 패턴을 사용하는 앱에서는 다음과 같은 어려운 문제를 해결해야 하므로 탐색이 복잡할 수 있습니다.
- 뷰 간의 긴밀한 결합 및 종속성을 도입하지 않는 방법을 사용하여 탐색할 뷰를 식별하는 방법입니다.
- 탐색할 뷰가 인스턴스화되고 초기화되는 프로세스를 조정하는 방법입니다. MVVM을 사용하는 경우 뷰 및 뷰 모델을 인스턴스화하고 뷰의 바인딩 컨텍스트를 통해 서로 연결해야 합니다. 앱이 종속성 주입 컨테이너를 사용하는 경우 뷰 및 뷰 모델의 인스턴스화에는 특정 생성 메커니즘이 필요할 수 있습니다.
- 뷰 우선 탐색을 수행할지 또는 모델 우선 탐색을 볼지 여부입니다. 뷰 우선 탐색을 사용하면 탐색할 페이지가 뷰 형식의 이름을 참조합니다. 탐색하는 동안 지정된 뷰는 해당 뷰 모델 및 기타 종속 서비스와 함께 인스턴스화됩니다. 다른 방법은 보기 모델 우선 탐색을 사용하는 것입니다. 여기서 탐색할 페이지는 보기 모델 형식의 이름을 참조합니다.
- 보기 및 보기 모델에서 앱의 탐색 동작을 깔끔하게 분리하는 방법입니다. MVVM 패턴은 앱의 UI와 프레젠테이션 및 비즈니스 논리를 구분합니다. 그러나 앱의 탐색 동작은 앱의 UI 및 프레젠테이션 부분에 걸쳐 있는 경우가 많습니다. 사용자는 뷰에서 탐색을 시작하는 경우가 많으며 탐색의 결과로 뷰가 대체됩니다. 그러나 보기 모델 내에서 탐색을 시작하거나 조정해야 하는 경우가 많습니다.
- 초기화를 위해 탐색하는 동안 매개 변수를 전달하는 방법입니다. 예를 들어 사용자가 뷰로 이동하여 주문 세부 정보를 업데이트하는 경우 올바른 데이터를 표시할 수 있도록 주문 데이터를 뷰에 전달해야 합니다.
- 특정 비즈니스 규칙이 준수되도록 탐색을 조정하는 방법입니다. 예를 들어 뷰에서 벗어나기 전에 잘못된 데이터를 수정하거나 뷰 내에서 변경한 데이터를 제출 또는 삭제하라는 메시지가 사용자에게 표시될 수 있습니다.
이 장에서는 모델 우선 보기 페이지 탐색을 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 인스턴스 만들기
인터페이스를 INavigationService
구현하는 클래스는 NavigationService
다음 코드 예제와 같이 Autofac 종속성 주입 컨테이너를 사용하여 싱글톤으로 등록됩니다.
builder.RegisterType<NavigationService>().As<INavigationService>().SingleInstance();
INavigationService
인터페이스는 다음 코드 예제에 ViewModelBase
설명된 대로 클래스 생성자에서 확인됩니다.
NavigationService = ViewModelLocator.Resolve<INavigationService>();
클래스의 메서드에서 NavigationService
만든 Autofac 종속성 주입 컨테이너에 저장된 개체에 대한 참조를 InitNavigation
App
반환합니다. 자세한 내용은 앱이 시작될 때 탐색을 참조하세요.
ViewModelBase
클래스는 NavigationService
인스턴스를 INavigationService
형식의 NavigationService
속성에 저장합니다. 따라서 클래스에서 파생되는 모든 뷰 모델 클래스는 ViewModelBase
이 속성을 사용하여 NavigationService
인터페이스에 지정된 메서드에 INavigationService
액세스할 수 있습니다. 이렇게 하면 Autofac 종속성 주입 컨테이너에서 각 뷰 모델 클래스에 개체를 삽입 NavigationService
하는 오버헤드가 방지됩니다.
탐색 요청 처리
Xamarin.FormsNavigationPage
는 사용자가 원하는 대로 페이지, 앞뒤로 탐색할 수 있는 계층적 탐색 환경을 구현하는 클래스를 제공합니다. 계층적 탐색에 대한 자세한 내용은 계층적 탐색을 참조하세요.
eShopOnContainers 앱은 클래스를 NavigationPage
직접 사용하는 대신 다음 코드 예제와 같이 클래스의 클래스 CustomNavigationView
를 래핑합니다NavigationPage
.
public partial class CustomNavigationView : NavigationPage
{
public CustomNavigationView() : base()
{
InitializeComponent();
}
public CustomNavigationView(Page root) : base(root)
{
InitializeComponent();
}
}
이 래핑의 목적은 클래스의 XAML 파일 내에서 인스턴스를 쉽게 스타일링 NavigationPage
하기 위한 것입니다.
탐색은 다음 코드 예제에 설명된 대로 탐색할 페이지의 보기 모델 형식을 지정하여 메서드 중 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 자식 네임스페이스입니다.
- 보기 이름은 "모델"이 제거된 모델 이름 보기에 해당합니다.
뷰가 인스턴스화되면 해당 뷰 모델과 연결됩니다. 이러한 상황이 발생하는 방법에 대한 자세한 내용은 뷰 모델 로케이터를 사용하여 뷰 모델 자동 만들기를 참조하세요.
생성되는 뷰가 a 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();
}
메서드는 Autofac 종속성 주입 컨테이너에 새 NavigationService
개체를 만들고 메서드를 호출하기 전에 해당 개체에 대한 참조를 반환합니다 InitializeAsync
.
참고 항목
인터페이스가 INavigationService
클래스에 의해 ViewModelBase
확인되면 컨테이너는 InitNavigation 메서드가 호출될 때 생성된 개체에 대한 참조 NavigationService
를 반환합니다.
다음 코드 예제에서는 메서드를 보여줍니다.NavigationService
InitializeAsync
public Task InitializeAsync()
{
if (string.IsNullOrEmpty(Settings.AuthAccessToken))
return NavigateToAsync<LoginViewModel>();
else
return NavigateToAsync<MainViewModel>();
}
MainView
앱에 인증에 사용되는 캐시된 액세스 토큰이 있는지 확인합니다. 그렇지 않으면 탐색 LoginView
됩니다.
Autofac 종속성 주입 컨테이너에 대한 자세한 내용은 종속성 주입 소개를 참조하세요.
탐색 중 매개 변수 전달
인터페이스에서 NavigateToAsync
INavigationService
지정한 메서드 중 하나를 사용하면 탐색 데이터를 탐색 중인 뷰 모델에 전달되는 인수로 지정할 수 있습니다. 이 인수는 일반적으로 초기화를 수행하는 데 사용됩니다.
예를 들어 ProfileViewModel
클래스에는 사용자가 ProfileView
페이지에서 주문을 선택할 때 실행되는 OrderDetailCommand
가 포함됩니다. 그러면 다음 코드 예제에 표시된 OrderDetailAsync
메서드가 실행됩니다.
private async Task OrderDetailAsync(Order order)
{
await NavigationService.NavigateToAsync<OrderDetailViewModel>(order);
}
이 메서드는 탐색을 호출하여 OrderDetailViewModel
사용자가 페이지에서 선택한 순서를 나타내는 인스턴스를 ProfileView
전달 Order
합니다. 클래스가 NavigationService
만들어 OrderDetailView
지면 클래스가 OrderDetailViewModel
인스턴스화되고 뷰 BindingContext
에 할당됩니다. 탐색한 OrderDetailView
후 메서드는 InternalNavigateToAsync
뷰의 연결된 뷰 모델의 메서드를 실행합니다 InitializeAsync
.
이 InitializeAsync
메서드는 클래스에서 재정의 ViewModelBase
할 수 있는 메서드로 정의됩니다. 이 메서드는 object
탐색 작업 중에 뷰 모델에 전달할 데이터를 나타내는 인수를 지정합니다. 따라서 탐색 작업에서 데이터를 수신하려는 뷰 모델 클래스는 필요한 초기화를 수행하는 메서드의 InitializeAsync
고유한 구현을 제공합니다. 다음 코드 예제에서는 OrderDetailViewModel
클래스의 InitializeAsync
메서드를 보여 줍니다.
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
합니다. 이 이벤트는 기본적으로 이벤트에 대한 이벤트 인수는 명령에 전달됩니다. 이 데이터는 WebNavigatingEventArgs
에서 Url
를 반환하는 EventArgsConverter
속성에 지정된 변환기를 통해 원본과 대상 간에 전달될 때 변환됩니다. 따라서 실행될 때 NavigationCommand
웹 페이지의 URL은 등록된 매개 변수로 전달됩니다 Action
.
그러면 NavigationCommand
이 다음 코드 예제에 표시된 NavigateAsync
메서드를 실행합니다.
private async Task NavigateAsync(string url)
{
...
await NavigationService.NavigateToAsync<MainViewModel>();
await NavigationService.RemoveLastFromBackStackAsync();
...
}
이 메서드는 탐색을 MainViewModel
호출하고 다음 탐색을 수행하면 LoginView
탐색 스택에서 페이지가 제거됩니다.
탐색 확인 또는 취소
사용자가 탐색을 확인 또는 취소할 수 있도록 앱이 탐색 작업 중에 사용자와 상호 작용해야 할 수 있습니다. 예를 들어 사용자가 데이터 입력 페이지를 완전히 작성하기 전에 탐색을 시도하는 경우에 필요할 수 있습니다. 이 경우 앱은 사용자가 페이지를 벗어나도록 허용하거나 탐색 작업이 발생하기 전에 해당 작업을 취소할 수 있는 알림을 제공해야 합니다. 이 작업은 알림의 응답을 사용하여 탐색이 호출되는지 여부를 제어하여 뷰 모델 클래스에서 수행할 수 있습니다.
요약
Xamarin.Forms 에는 내부 논리 기반 상태 변경의 결과로 일반적으로 사용자가 UI와 상호 작용하거나 앱 자체에서 발생하는 페이지 탐색에 대한 지원이 포함됩니다. 그러나 탐색은 MVVM 패턴을 사용하는 앱에서 구현하기 복잡할 수 있습니다.
이 장에서는 NavigationService
뷰 모델에서 모델 우선 보기 탐색을 수행하는 데 사용되는 클래스를 제시했습니다. 보기 모델 클래스에 탐색 논리를 배치하면 자동화된 테스트를 통해 논리를 실행할 수 있습니다. 또한 뷰 모델은 특정 비즈니스 규칙이 적용되도록 탐색을 제어하는 논리를 구현할 수 있습니다.