In this second post of our on-going blog series, we'll describe app navigation logic and how we've tried to create an intuitive/straightforward UX for a variety of usage scenarios.
If you are new to this series, please be sure to review our first post for background information on this app.
Navigation Between Top Level App Screens
In our previous post, we detailed several top-level app screens (screens that allow users to check market state, track their favorite stocks, and monitor hypothetical portfolio gains/losses). As you may already know, we will use our Navigation Drawer component to navigate between individual levels:
The DrawerPage page allows us to create a list with target destinations to the drawer and display content in the main area. The DrawerPage offers swipe gesture support and multiple appearance options.
In the following code snippet, we customize the drawer page so our app mirrors the UI design displayed above.
View Markup
<dxn:DrawerPage xmlns:dxn="clr-namespace:DevExpress.XamarinForms.Navigation;assembly=DevExpress.XamarinForms.Navigation" x:Class="Stocks.UI.Views.MainPage"> ... <dxn:DrawerPage.BindingContext> <viewmodels:MainViewModel> </dxn:DrawerPage.BindingContext> <dxn:DrawerPage.DrawerHeaderContent> <views:MenuHeader Style="{StaticResource DrawerHeaderStyle}"/> </dxn:DrawerPage.DrawerHeaderContent> <dxn:DrawerPage.DrawerContent> <ListView ItemsSource="{Binding Children}" Style="{StaticResource DrawerContentStyle}" ItemTapped="OnPageListItemTapped"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <views:MenuRow Style="{StaticResource DrawerContentItemStyle}"/> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </dxn:DrawerPage.DrawerContent> <dxn:DrawerPage.MainContent> <ContentPage BackgroundColor="{StaticResource BackgroundColor}"/> </dxn:DrawerPage.MainContent> </dxn:DrawerPage>
View Code Behind
public partial class MainPage : DrawerPage, INavigationHost { MainViewModel ViewModel => (MainViewModel)BindingContext; public MainPage() { InitializeComponent(); } private void OnPageListItemTapped(object sender, ItemTappedEventArgs e) { this.IsDrawerOpened = false; if (!(e.Item is BaseViewModel child)) return; ViewModel.SelectedChild = child; } void ToggleDrawer(object sender) { IsDrawerOpened = !IsDrawerOpened; } }
View Model Code
public class MainViewModel: BaseViewModel { BaseViewModel selectedChild; public IEnumerable<BaseViewModel> Children { get; } public BaseViewModel SelectedChild { get => selectedChild; set => SetProperty( ref selectedChild, value, onChanged: (oldValue, newValue) => { if (oldValue != null) oldValue.IsSelected = false; if (newValue != null) newValue.IsSelected = true; NavigationService.Replace(oldValue, newValue); } ); } // Other properties. public MainViewModel(IViewModelFactory viewModelFactory, INavigationService navigationService): base(navigationService) { Children = new List<BaseViewModel> { viewModelFactory.CreateMarketViewModel(), viewModelFactory.CreateMarketNewsViewModel(), viewModelFactory.CreateWatchlistViewModel(), viewModelFactory.CreatePortfolioViewModel() }; // Initialization of other properties. } }
NavigationService creates a new page by view model type and assigns it to the MainContent property of the main page.
Data Grouping
Since our app displays large chunks of data, we thought it important to group information by categories (by using tabs). For example, the Ticker Detail page displays 3 tabs – each with different information/unique visualization:
The DevExpress Xamarin UI suite includes two tab components: TabPage and TabView. These controls offer configurable headers, support item sources and templates, and offer a wide variety of behavioral customization options.
Since we must initialize view models when pages appear, we’ll use the DevExpress Xamarin TabPage (our TabView does not support this behavior).
In the following code snippet, we customize the TabPage so our app mirrors the UI design displayed above.
View Markup
<dxn:TabPage xmlns:dxn="clr-namespace:DevExpress.XamarinForms.Navigation;assembly=DevExpress.XamarinForms.Navigation" x:Class="Stocks.UI.Views.SymbolPage" Title="{Binding Title}" ItemsSource="{Binding Children}" SelectedItem="{Binding SelectedChild}" ItemTemplate="{utils:PageLocatorTemplateSelector}"> <dxn:TabPage.BindingContext> <viewmodels:SymbolViewModel> </dxn:TabPage.BindingContext> <dxn:TabPage.Resources> <themes:SymbolPageStyle> </dxn:TabPage.Resources> <dxn:TabPage.ItemHeaderTemplate> <DataTemplate> <views:TabHeader Style="{StaticResource TabHeaderStyle}"/> </DataTemplate> </dxn:TabPage.ItemHeaderTemplate> </dxn:TabPage>
View Model Code
public class SymbolViewModel : BaseViewModel { BaseViewModel selectedChild; public override string Title { get; private set; } public ICollection<BaseViewModel> Children { get; private set; } public BaseViewModel SelectedChild { get => selectedChild; set => SetProperty(ref selectedChild, value, onChanged: (oldV, v) => { if (oldV != null) { oldV.IsSelected = false; } if (v != null) { v.IsSelected = true; } }); } public SymbolViewModel(string ticker, string companyName, IViewModelFactory viewModelFactory, INavigationService navigationService): base(navigationService) { Title = companyName; Children = new List<BaseViewModel> { viewModelFactory.CreateSymbolChartViewModel(ticker), viewModelFactory.CreateInfoViewModel(ticker), viewModelFactory.CreateNewsListViewModel(ticker) }; SelectedChild = Children[0]; } }
Carousel
Our last navigation related requirement is use of a chart carousel within the Portfolio page:
As you can see below, we will use our TabView to emulate a chart "carousel" (the TabView will be embedded in the page layout).
View Markup
<ContentPage xmlns:dxn="clr-namespace:DevExpress.XamarinForms.Navigation;assembly=DevExpress.XamarinForms.Navigation"> <ContentPage.BindingContext> <viewmodels:PortfolioViewModel/> </ContentPage.BindingContext> <ContentPage.Resources> <ResourceDictionary> <DataTemplate x:Key="headerItemTemplate"> <dxe:IconView ForegroundColor="{Binding IsSelected, Converter={converters:BoolToColorConverter TrueColor={StaticResource AccentColor}, FalseColor={StaticResource PortfolioPage_TabViewHeaderItemColor}}}" ImageSource="circle.svg" Style="{StaticResource CarouselIndicatorStyle}"> </dxe:IconView> </DataTemplate> <DataTemplate x:Key="lineChartTemplate"> <views:PortfolioLineChart/> </DataTemplate> <DataTemplate x:Key="donutChartTemplate"> <views:PortfolioDonutChart/> </DataTemplate> <DataTemplate x:Key="barChartTemplate"> <views:PortfolioBarChart/> </DataTemplate> <utils:PortfolioChartTemplateSelector x:Key="chartSelector" LineChartTemplate="{StaticResource lineChartTemplate}" DonutChartTemplate="{StaticResource donutChartTemplate}" BarChartTemplate="{StaticResource barChartTemplate}"> </utils:PortfolioChartTemplateSelector> <!-- Other resources --> </ResourceDictionary> </ContentPage.Resources> <ScrollView> <Grid Style="PortfolioContainerStyle"> <dxn:TabView ItemsSource="{Binding Charts}" ItemTemplate="{StaticResource chartSelector}" SelectedItem="{Binding SelectedChart, Mode=TwoWay}" Style="{StaticResource CarouselStyle}"/> <!-- Other views. --> </Grid> </ScrollView> </ContentPage>
View Model Code
class PortfolioViewModel { // Other fields. public override string Title => "Portfolio"; public IEnumerable<PortfolioChartViewModel> Charts { get; } public PortfolioChartViewModel SelectedChart { get => selectedChart; set => SetProperty(ref selectedChart, value, onChanged: (oldV, newV) => { if (oldV != null) oldV.IsSelected = false; if (newV != null) newV.IsSelected = true; }); } // Other properties. public PortfolioViewModel(ILocalStorage localStorage, IListSymbolRepository listSymbolRepository, IHistoricalPriceRepository priceRepository, IViewModelFactory viewModelFactory, INavigationService navigationService) : base(navigationService) { //Other class initialization. Charts = new List<PortfolioChartViewModel>() { new PortfolioLineChartViewModel(), new PortfolioDonutChartViewModel(), new PortfolioBarChartViewModel(OpenBarChartDetail) }; SelectedChart = charts[0]; InitializePortfolioIfNeeds(); } }
What's next
With major navigation related tasks complete, we’ll move our focus to data rendering/presentation and show you how we leveraged the flexibility of our Xamarin UI controls to deliver both an elegant and easy-to-use mobile interface.
Your Feedback Matters
As always, if you’ve yet to try our Xamarin.Forms UI controls or would like to discuss your business needs, please comment below or write to us at info@devexpress.com.