Xamarin.Forms UI Controls - Porting our Mobile Stock Market App to the Xamarin Platform (Part 2)

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.

NOTE: At present, DevExpress Xamarin.Forms UI controls ship as part of our Universal Subscription. We expect to release a Xamarin-only product subscription in our v19.2 release cycle.

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:

Navigation Drawer

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:

Tab Page

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:

Tab View

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.

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.
No Comments

Please login or register to post comments.