Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Xamarin.Forms.Shell Spec #2415

Closed
22 of 26 tasks
jassmith opened this issue Apr 10, 2018 · 199 comments · Fixed by #4522
Closed
22 of 26 tasks

Xamarin.Forms.Shell Spec #2415

jassmith opened this issue Apr 10, 2018 · 199 comments · Fixed by #4522

Comments

@jassmith
Copy link

jassmith commented Apr 10, 2018

Xamarin.Forms Shell Spec

Make it simple and straightforward for new developers to get a complete app experience that is properly structured, uses the right elements, with very little effort and a clear path to being good by default.

Shell is an opinionated API at points, it does not always default every value to some .Default which may change based on the running platform. Instead it may set a value which is then respected across all platforms regardless what the platform default is.

Note: Drawing Spec moved to: #2452

Visual Treatment

Needs screenshots...

Shell Hierarchy

Understanding the Shell hierarchy at first can seem overwhelming at first. It represents a complex hierarchy and provides powerful mechanisms for minimizing the amount of boilerplate xaml required to create rich hierarchies.

Thus when first learning shell it is easiest to learn without the shortcuts first, and then introduce the shortcuts to see how to easily minimize the amount of XAML being written.

Note that all samples that follow do not use templated ShellContent's, which are discussed elsewhere in the spec. Failure to properly use ShellContents with a ContentTemplate will result in all pages being loaded at startup, which will negatively impact startup performance. These samples are for learning purposes only.

Fortunately using ShellContents with ContentTemplates is usually more concise than not using them.

No shortcuts

Many of these samples will look needlessly complex, and in reality they are. In the next section these will get simplified.

A simple one page app

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:local="clr-namespace:MyStore"
       FlyoutBehavior="Disabled"
       x:Class="MyStore.Shell">

  <ShellItem>
    <ShellSection>
      <ShellContent>
        <local:HomePage />
      </ShellContent>
    </ShellSection>
  </ShellItem>
</Shell>

step1

The top bar can be hidden entirely by setting Shell.NavBarVisible="false" on the MainPage. The Flyout would also look rather sparse in this mode and is thus disabled in this sample.

Two page app with bottom tabs

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:local="clr-namespace:MyStore"
       FlyoutBehavior="Disabled"
       x:Class="MyStore.Shell">

  <ShellItem>

    <ShellSection Title="Home" Icon="home.png">
      <ShellContent>
        <local:HomePage />
      </ShellContent>
    </ShellSection>

    <ShellSection Title="Notifications" Icon="bell.png">
      <ShellContent>
        <local:NotificationsPage />
      </ShellContent>
    </ShellSection>

  </ShellItem>
</Shell>

step2

By adding a section ShellSection to the ShellItem another bottom tab appears. Settings the approriate title and icon allows control over the Tab item title and icon.

Two page app with top and bottom tabs

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:local="clr-namespace:MyStore"
       FlyoutBehavior="Disabled"
       x:Class="MyStore.Shell">

  <ShellItem>

    <ShellSection Title="Home" Icon="home.png">
      <ShellContent>
        <local:HomePage />
      </ShellContent>
    </ShellSection>

    <ShellSection Title="Notifications" Icon="bell.png">

      <ShellContent Title="Recent">
        <local:NotificationsPage />
      </ShellContent>

      <ShellContent Title="Alert Settings">
        <local:SettingsPage />
      </ShellContent>

    </ShellSection>

  </ShellItem>
</Shell>

step3

By adding a second ShellContent to the ShellSection a top tab bar is added and the pages can be flipped between when the bottom tab is selected.

Two page app using flyout navigation

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:local="clr-namespace:MyStore"
       x:Class="MyStore.Shell">

  <Shell.FlyoutHeader>
    <local:FlyoutHeader />
  </Shell.FlyoutHeader>

  <ShellItem Title="Home" Icon="home.png">
    <ShellSection>
      <ShellContent>
        <local:HomePage />
      </ShellContent>
    </ShellSection>
  </ShellItem>

  <ShellItem Title="Notifications" Icon="bell.png">
    <ShellSection>
      <ShellContent>
        <local:NotificationsPage />
      </ShellContent>
    </ShellSection>
  </ShellItem>
</Shell>

step4

The flyout is re-enabled in this sample. Here the user can switch between the two pages using the flyout as an intermediary. A header has also been added to look nice.

Using shorthand syntax

Now that all levels of the hierarchy have been shown and breifly explained, it is possible to leave most of the unneeded wrapping out when defining a hierarchy. Shell only ever contains ShellItems which only ever contain ShellSections which in turn only ever contain ShellContents. However there are implicit conversion operators in place to allow for automatic up-wrapping.

A simple one page app

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:local="clr-namespace:MyStore"
       FlyoutBehavior="Disabled"
       x:Class="MyStore.Shell">

  <local:HomePage />
</Shell>

With implicit wrapping the Page is automatically wrapped all the way up to a ShellItem. It is not needed to write out all the intermiedate layers. The Title and Icon from the Page will be bound up to any implicitly generated parents.

Two page app with bottom tabs

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:local="clr-namespace:MyStore"
       FlyoutBehavior="Disabled"
       x:Class="MyStore.Shell">

  <ShellItem>
    <local:HomePage Icon="home.png" />
    <local:NotificationsPage Icon="bell.png" />
  </ShellItem>
</Shell>

The pages are now implicitly wrapped up into ShellContent and their own ShellSections. This results in two different tabs being created, just like before.

Two page app with top and bottom tabs

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:local="clr-namespace:MyStore"
       FlyoutBehavior="Disabled"
       x:Class="MyStore.Shell">

  <ShellItem>
    <local:HomePage Icon="home.png" />

    <ShellSection Title="Notifications" Icon="bell.png">
        <local:NotificationsPage />
        <local:SettingsPage />
    </ShellSection>
  </ShellItem>
</Shell>

Two page app using flyout navigation

<Shell xmlns="http://xamarin.com/schemas/2014/forms"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:local="clr-namespace:MyStore"
       x:Class="MyStore.Shell">

  <Shell.FlyoutHeader>
    <local:FlyoutHeader />
  </Shell.FlyoutHeader>

  <local:HomePage Icon="home.png" />
  <local:NotificationsPage Icon="bell.png" />
</Shell>

Here the implicit wrapping goes all the way up to shell item so there is no need for us to do any wrapping ourselves.

Navigation Model

Push Navigation

All navigation is based on the View’s .Navigation property. Pushes go into the current ShellSection that is displayed. This means in a push event the top tabs will go away and the bottom tabs will remain.

URI Navigation

The Shell can be navigated using the standard Navigation property as discussed above, however shell introduces a far more convenient navigation mechanism.

Navigation URI's are formatted as follows:

[Shell.RouteScheme]://[Shell.RouteHost]/[Shell]/[ShellItem]/[ShellSection]/[ShellContent]/[NavStack1]/[NavStack2]...

All items in the shell hierarchy have a route associated with them. If the route is not set by the developer, the route is generated at runtime. Runtime generated routes are not gauranteed to be stable across different runs of the app and thus are not useful for deep linking.

Handling the Back Button

Since Shell will be in the enviable position of not having to use native controls, all forms of back button overriding can and should be supported.

Proper handling of the back button needs to support the following features:

  • Intercepting interactions and stopping them
  • Replacing the back button with something else
  • Hiding the back button entirely when desired
  • Work for software and hardware buttons

The API needs to be granular to the page level for ease of use, but also be able to be handled further up the stack in a more general level.

Code Samples and API

Samples

<Shell>
  <Shell.FlyoutHeaderTemplate>
    <DataTemplate>
      <Grid>
        <Label Text="{x:Bind HeaderText}" />
      </Grid>
    </DataTemplate>
  </Shell.FlyoutHeaderTemplate>
  
  // Flyout Item 1
  <ShellItem Title="My music" ItemsSource="{x:Bind MyMusicModels}" TabLocation="Bottom">
    <ShellItem.ItemTemplate>
      <local:MyMusicItemTemplateSelection />
    </ShellItem.ItemTemplate>
  </ShellItem>
  
  // Flyout Item 2
  <ShellItem Title="Home" Icon="home.png" GroupBehavior="ShowTabs">
    <ShellContent Title="My Friends">
      <local:FriendsPage />
    </ShellContent>
    <local:FeedPage />
    <local:ProfilePage />
  </ShellItem>
  
  // Flyout Item 3
  <local:SettingsPage />
  
  // Flyout Item 4
  <MenuItem Text="Log Out" Command="{x:Bind LogOutCommand}" />
</Shell>

Single Page App using Shell

<Shell FlyoutVisibility="Disabled">
  <local:MyPage />
</Shell>

Single Group of Tabs app (no Flyout)

<Shell FlyoutVisibility="Disabled">
  <ShellItem>
    <local:MyPage />
    <local:MySecondPage />
    <local:MyThirdPage />
  </ShellItem>
</Shell>

Multiple pages in flyout with no tabs

<Shell FlyoutVisibility="Disabled">
  <local:MyPage />
  <local:MySecondPage />
  <local:MyThirdPage />
</Shell>

Single Page Searchable App

<Shell FlyoutVisibility="Disabled">
  <local:MyPage />
</Shell>
public class MyPage : ContentPage
{
  public class MyPageSearchHandler : SearchHandler
  {
    public MyPageHandler ()
    {
      SearchBoxVisibility = SearchBoxVisibility.Collapsed;
      IsSearchEnabled = true;
      Placeholder = "Search me!";
    }

    protected override async void OnSearchConfirmed (string query)
    {
      IsSearching = true;
    
      await PerformSearch (query);
      UpdateResults ();
      
      IsSearching = false;
    }
    
    protected override void OnSearchChanged (string oldValue, string newValue)
    {
      // Do nothing, we will wait for confirmation
    }
  }

  public MyPage ()
  {
    Shell.SetSearchHandler (this, new MyPageSearchHandler ());
  }
}

API Definition

Shell

This is the base class for Shell's. It defines a somewhat strict navigational model however all shells must adhere to it in general. It does not include any theming or design language specific features.

[ContentProperty("Items")]
public class Shell : Page, IShellController
{
  // Attached Properties
  public static readonly BindableProperty BackButtonBehaviorProperty;
  public static readonly BindableProperty FlyoutBehaviorProperty;
  public static readonly BindableProperty NavBarVisibleProperty;
  public static readonly BindableProperty SearchHandlerProperty;
  public static readonly BindableProperty SetPaddingInsetsProperty;
  public static readonly BindableProperty TabBarVisibleProperty;
  public static readonly BindableProperty TitleViewProperty;
  public static readonly BindableProperty ShellBackgroundColorProperty;
  public static readonly BindableProperty ShellDisabledColorProperty;
  public static readonly BindableProperty ShellForegroundColorProperty;
  public static readonly BindableProperty ShellTabBarBackgroundColorProperty;
  public static readonly BindableProperty ShellTabBarDisabledColorProperty;
  public static readonly BindableProperty ShellTabBarForegroundColorProperty;
  public static readonly BindableProperty ShellTabBarTitleColorProperty;
  public static readonly BindableProperty ShellTabBarUnselectedColorProperty;
  public static readonly BindableProperty ShellTitleColorProperty;
  public static readonly BindableProperty ShellUnselectedColorProperty;

  public static BackButtonBehavior GetBackButtonBehavior(BindableObject obj);
  public static FlyoutBehavior GetFlyoutBehavior(BindableObject obj);
  public static bool GetNavBarVisible(BindableObject obj);
  public static SearchHandler GetSearchHandler(BindableObject obj);
  public static bool GetSetPaddingInsets(BindableObject obj);
  public static bool GetTabBarVisible(BindableObject obj);
  public static View GetTitleView(BindableObject obj);
  public static void SetBackButtonBehavior(BindableObject obj, BackButtonBehavior behavior);
  public static void SetFlyoutBehavior(BindableObject obj, FlyoutBehavior value);
  public static void SetNavBarVisible(BindableObject obj, bool value);
  public static void SetSearchHandler(BindableObject obj, SearchHandler handler);
  public static void SetSetPaddingInsets(BindableObject obj, bool value);
  public static void SetTabBarVisible(BindableObject obj, bool value);
  public static void SetTitleView(BindableObject obj, View value);
  public static Color GetShellBackgroundColor(BindableObject obj);
  public static Color GetShellDisabledColor(BindableObject obj);
  public static Color GetShellForegroundColor(BindableObject obj);
  public static Color GetShellTabBarBackgroundColor(BindableObject obj);
  public static Color GetShellTabBarDisabledColor(BindableObject obj);
  public static Color GetShellTabBarForegroundColor(BindableObject obj);
  public static Color GetShellTabBarTitleColor(BindableObject obj);
  public static Color GetShellTabBarUnselectedColor(BindableObject obj);
  public static Color GetShellTitleColor(BindableObject obj);
  public static Color GetShellUnselectedColor(BindableObject obj);
  public static void SetShellBackgroundColor(BindableObject obj, Color value);
  public static void SetShellDisabledColor(BindableObject obj, Color value);
  public static void SetShellForegroundColor(BindableObject obj, Color value);
  public static void SetShellTabBarBackgroundColor(BindableObject obj, Color value);
  public static void SetShellTabBarDisabledColor(BindableObject obj, Color value);
  public static void SetShellTabBarForegroundColor(BindableObject obj, Color value);
  public static void SetShellTabBarTitleColor(BindableObject obj, Color value);
  public static void SetShellTabBarUnselectedColor(BindableObject obj, Color value);
  public static void SetShellTitleColor(BindableObject obj, Color value);
  public static void SetShellUnselectedColor(BindableObject obj, Color value);

  // Bindable Properties
  public static readonly BindableProperty CurrentItemProperty;
  public static readonly BindableProperty CurrentStateProperty;
  public static readonly BindableProperty FlyoutBackgroundColorProperty;
  public static readonly BindableProperty FlyoutHeaderBehaviorProperty;
  public static readonly BindableProperty FlyoutHeaderProperty;
  public static readonly BindableProperty FlyoutHeaderTemplateProperty;
  public static readonly BindableProperty FlyoutIsPresentedProperty;
  public static readonly BindableProperty GroupHeaderTemplateProperty;
  public static readonly BindableProperty ItemsProperty;
  public static readonly BindableProperty ItemTemplateProperty;
  public static readonly BindableProperty MenuItemsProperty;
  public static readonly BindableProperty MenuItemTemplateProperty;

  public Shell();

  public event EventHandler<ShellNavigatedEventArgs> Navigated;

  public event EventHandler<ShellNavigatingEventArgs> Navigating;

  public ShellItem CurrentItem {get; set;}

  public ShellNavigationState CurrentState {get; }

  public Color FlyoutBackgroundColor {get; set;}

  public FlyoutBehavior FlyoutBehavior {get; set;}

  public object FlyoutHeader {get; set;}

  public FlyoutHeaderBehavior FlyoutHeaderBehavior {get; set;}

  public DataTemplate FlyoutHeaderTemplate {get; set;}

  public bool FlyoutIsPresented {get; set;}

  public DataTemplate GroupHeaderTemplate {get; set;}

  public ShellItemCollection Items {get; }

  public DataTemplate ItemTemplate {get; set;}

  public MenuItemCollection MenuItems {get; }

  public DataTemplate MenuItemTemplate {get; set;}

  public string Route {get; set;}

  public string RouteHost { get; set; }

  public string RouteScheme { get; set; }

  public async Task GoToAsync(ShellNavigationState state, bool animate = true);

  protected virtual void OnNavigated(ShellNavigatedEventArgs args);

  protected virtual void OnNavigating(ShellNavigatingEventArgs args);
}

Description

Attached Properties:

API Description
SearchHandlerProperty Attached property for the definition of a page level Search capability. Can be attached at multiple points in the hierarchy. Uses some of the features defined here https://material.io/guidelines/patterns/search.html#search-in-app-search
BackButtonBehavior Allows complete override of how the back button behaves. Applies to on-screen and physical back buttons.
FlyoutBehavior Defines how the Flyout should present itself. This can be attached to ShellItems, ShellContents, or Pages to override the default behavior.
NavBarVisibleProperty Property set on a Page to define if the NavBar should be visible when it is presented
SetPaddingInsetsProperty Setting this property on a Page will cause its Padding to be set such that its content will not flow under any Shell chrome.
TabBarVisibleProperty Property set on a Page to define if the TabBar should be visible when it is presented
TitleViewProperty Property set on a Page to define the TitleView
ShellBackgroundColorProperty Describes the background color which should be used for the chrome elements of the Shell. This color will not fill in behind the Content of the Shell.
ShellDisabledColorProperty The color to shade text/icons which are disabled
ShellForegroundColorProperty The color to shade normal text/icons in the shell
ShellTabBarBackgroundColorProperty Override of ShellBackgroudnColor for the TabBar. If not set ShellBackgroundColor will be used
ShellTabBarDisabledColorProperty Override of ShellDisabledColor for the TabBar. If not set ShellDisabledColorProperty will be used
ShellTabBarForegroundColorProperty Override of ShellForegroundColorProperty for the TabBar. If not set ShellForegroundColorProperty will be used
ShellTabBarTitleColorProperty Override of ShellTitleColorProperty for the TabBar. If not set ShellTitleColorProperty will be used
ShellTabBarUnselectedColorProperty Override of ShellUnselectedColorProperty for the TabBar. If not set ShellUnselectedColorProperty will be used
ShellTitleColorProperty The color used for the title of the current Page
ShellUnselectedColorProperty The color used for unselected text/icons in the shell chrome

Properties:

API Description
CurrentItem The currently selected ShellItem
CurrentState The current navigation state of the Shell. Passing this state back to GoToAsync will cause the Shell to return to navigate back state.
FlyoutBackgroundColorProperty The background color of the Flyout menu
FlyoutBehavior Sets the default FlyoutBehavior for the Shell.
FlyoutHeader The object used as the Header of the Flyout. If FlyoutHeaderTemplate is not null, this is passed as the BindingContext to the inflated object. If FlyoutHeaderTemplate is null and FlyoutHeader is of type View it will be used directly. Otherwise it will be shown by calling ToString() and displaying the result.
FlyoutHeaderBehavior Sets the behavior of the FlyoutHeader when the Flyout needs to be scrolled to show contents.
FlyoutHeaderTemplate The template used to create a header for the Flyout.
FlyoutIsPresented Gets or sets whether or not the Flyout is current visible
GroupHeaderTemplate The DataTemplate used to show a Group Header if a ShellItem requests to be displayed as a Group of Tabs in the Flyout. If null, no header is displayed.
Items The primary Content of a Shell. Items define the list of items that will show in the Flyout as well as the content that will be displayed when a sidebar item is selected.
ItemTemplate The DataTemplate used to show items from the Items collection in the Flyout. Allows the developer to control visuals in the Flyout.
MenuItems A collection of MenuItems which will show up in the flyout in their own section.
MenuItemTemplate The DataTemplate to use when displayed a MenuItem in the Flyout.
Route The route part to address this element when performing deep linking.
RouteHost The string to place in the host portion of the URI generated when creating deep-links
RouteScheme The string to place in the scheme portion of the URI generated when creating deep-links

Public Methods:

API Description
GoToAsync Navigate to a ShellNavigationState. Returns a Task which will complete once the animation has completed.

Public Events:

API Description
Navigating The Shell is about to perform a Navigation either due to user interaction or the developer calling an API. The developer may cancel Navigation here if possible.
Navigated The Shell has completed a Navigation event.

ShellItemCollection

A collection for ShellItems

public sealed class ShellItemCollection : IEnumerable<ShellItem>, IList<ShellItem>

MenuItemCollection

A collection for MenuItems

public sealed class MenuItemCollection : IEnumerable<MenuItem>, IList<MenuItem>

ShellSectionCollection

A collection for ShellSections

public sealed class ShellSectionCollection : IEnumerable<ShellSection>, IList<ShellSection>

ShellContentCollection

A collection for ShellContents

public sealed class ShellContentCollection : IEnumerable<ShellContent>, IList<ShellContent>

ShellNavigatingEventArgs

An EventArgs used to describe a navigation event which is about to occur. The ShellNavigationEventArgs may also be used to cancel the navigation event if the developer desires.

public class ShellNavigatingEventArgs : EventArgs
{
  public ShellNavigationState Current { get; }

  public ShellNavigationState Target { get; }

  public ShellNavigationSource Source { get; }

  public bool CanCancel { get; }

  public bool Cancel ();
}

Properties:

API Description
Current The current NavigationState of the Shell. Calling GoToAsync with this ShellNavigationState will effectively undo this navigation event.
Target The state the Shell will be in after this navigation event completes.
Source The type of navigation which occurred to trigger this event. There may be multiple flags set.
CanCancel Whether or not the navigation event is cancellable. Not all events can be cancelled.

Public Methods:

API Description
Cancel Cancels the current navigation event. Returns true if the cancellation was succesful.

ShellNavigatedEventArgs

public class ShellNavigatedEventArgs : EventArgs
{
  public ShellNavigationState Previous { get; }

  public ShellNavigationState Current { get; }

  public ShellNavigationSource Source { get; }
}

Properties:

API Description
Previous The prior NavigationState of the Shell.
Current The new state the Shell is in when this navigation event completed.
Source The type of navigation which occurred to trigger this event. There may be multiple flags set.

ShellNavigationState

public class ShellNavigationState
{
  public Uri Location { get; set; }

  public ShellNavigationState ();
  public ShellNavigationState (string location);
  public ShellNavigationState (Uri uri);

  public static implicit operator ShellNavigationState (Uri uri);
  public static implicit operator ShellNavigationState (String value);
}

Properties:

API Description
Location The Uri which describes the navigation state of the Shell

Constructors:

API Description
(void) Creates a new ShellNavigationState with a null Location
(String) Creates a new ShellNavigationState with the Location set to the supplied string
(Uri) Creates a new ShellNavigationState with the Location set to the supplied Uri

ShellNavigationSource

[Flags]
public enum ShellNavigationSource
{
  Unknown = 0,
  Push,
  Pop,
  PopToRoot,
  Insert,
  Remove,
  ShellItemChanged,
  ShellSectionChanged,
  ShellContentChanged,
}

BaseShellItem

public class BaseShellItem : NavigableElement
{
  public static readonly BindableProperty FlyoutIconProperty;
  public static readonly BindableProperty IconProperty;
  public static readonly BindableProperty IsCheckedProperty;
  public static readonly BindableProperty IsEnabledProperty;
  public static readonly BindableProperty TitleProperty;

  public ImageSource FlyoutIcon { get; set; }

  public ImageSource Icon { get; set; }

  public bool IsChecked { get; }

  public bool IsEnabled { get; set; }

  public string Route { get; set; }

  public string Title { get; set; }
}

Properties:

API Description
FlyoutIcon The default icon to use when displayed in the Flyout. Will default to Icon if not set.
Icon The icon to display in parts of the chrome which are not the Flyout.
IsChecked If the BaseShellItem is currently checked in the Flyout (and thus should be highlighted)
IsEnabled If the the BaseShellItem is selectable in the chrome
Route Equivelant to setting Routing.Route
Title The Title to display in the UI

ShellGroupItem

public class ShellGroupItem : BaseShellItem
{
  public static readonly BindableProperty FlyoutDisplayOptionsProperty;;

  public FlyoutDisplayOptions FlyoutDisplayOptions { get; set; }
}

Properties:

API Description
FlyoutDisplayOptions Controls how this item and its children are displayed in the flyout

ShellItem

[ContentProperty("Items")]
public class ShellItem : ShellGroupItem, IShellItemController, IElementConfiguration<ShellItem>
{
  public static readonly BindableProperty CurrentItemProperty;

  public ShellItem();

  public ShellSection CurrentItem { get; set; }

  public ShellSectionCollection Items;

  public static implicit operator ShellItem(ShellSection shellSection);

  public static implicit operator ShellItem(ShellContent shellContent);

  public static implicit operator ShellItem(TemplatedPage page);

  public static implicit operator ShellItem(MenuItem menuItem);
}

Properties:

API Description
CurrentItem The selected ShellSection.
Items The ShellSectionCollection which is the primary content of a ShellItem. This collection defines all the tabs within a ShellItem.

ShellSection

[ContentProperty("Items")]
public class ShellSection : ShellGroupItem, IShellSectionController
{
  public static readonly BindableProperty CurrentItemProperty;
  public static readonly BindableProperty ItemsProperty

  public ShellSection();

  public ShellContent CurrentItem { get; set; }

  public ShellContentCollection Items { get; }

  public IReadOnlyList<Page> Stack { get; }

  public static implicit operator ShellSection(ShellContent shellContent);

  public virtual async Task GoToAsync(List<string> routes, IDictionary<string, string> queryData, bool animate);

  protected virtual IReadOnlyList<Page> GetNavigationStack();

  protected virtual void OnInsertPageBefore(Page page, Page before);

  protected async virtual Task<Page> OnPopAsync(bool animated);

  protected virtual async Task OnPopToRootAsync(bool animated);

  protected virtual Task OnPushAsync(Page page, bool animated);

  protected virtual void OnRemovePage(Page page);
}

Properties:

API Description
CurrentItem The selected ShellContent of the ShellSection.
Items The ShellContentCollection which is the root content of the ShellSection.
Stack The current pushed navigation stack on the ShellSection.

Methods:

API Description
GoToAsync Used by deep-linking to navigate to a known location. Should not need to be called directly in most cases.
GetNavigationStack Returns the current navigation stack
OnInsertPageBefore Called when the INavigation interfaces InsertPageBefore method is called
OnPopAsync Called when the INavigation interfaces PopAsync method is called
OnPopToRootAsync Called when the INavigation interfaces PopToRootAsync method is called
OnPushAsync Called when the INavigation interfaces PushAsync method is called
OnRemovePage Called when the INavigation interfaces RemovePage method is called

ShellContent

[ContentProperty("Content")]
public class ShellContent : BaseShellItem, IShellContentController
{
  public static readonly BindableProperty ContentProperty;
  public static readonly BindableProperty ContentTemplateProperty;
  public static readonly BindableProperty MenuItemsProperty;

  public ShellContent();

  public object Content { get; set; }

  public DataTemplate ContentTemplate { get; set; }

  public MenuItemCollection MenuItems { get; }

  public static implicit operator ShellContent(TemplatedPage page);
}

Properties:

API Description
Content The Content of a ShellContent. Usually a ContentPage or the BindingContext of the Page inflated by the ContentTemplate
ContentTemplate Used to dynamically inflate the content of the ShellContent. The Content property will be set as the BindingContext after inflation.
MenuItems The items to display in the Flyout when this ShellContent is the presented page

SearchHandler

public class SearchHandler : BindableObject, ISearchHandlerController
{
  public static readonly BindableProperty ClearIconHelpTextProperty;
  public static readonly BindableProperty ClearIconNameProperty;
  public static readonly BindableProperty ClearIconProperty;
  public static readonly BindableProperty ClearPlaceholderCommandParameterProperty;
  public static readonly BindableProperty ClearPlaceholderCommandProperty;
  public static readonly BindableProperty ClearPlaceholderEnabledProperty;
  public static readonly BindableProperty ClearPlaceholderHelpTextProperty;
  public static readonly BindableProperty ClearPlaceholderIconProperty;
  public static readonly BindableProperty ClearPlaceholderNameProperty;
  public static readonly BindableProperty CommandParameterProperty;
  public static readonly BindableProperty CommandProperty;
  public static readonly BindableProperty DisplayMemberNameProperty;
  public static readonly BindableProperty IsSearchEnabledProperty;
  public static readonly BindableProperty ItemsSourceProperty;
  public static readonly BindableProperty ItemTemplateProperty;
  public static readonly BindableProperty PlaceholderProperty;
  public static readonly BindableProperty QueryIconHelpTextProperty;
  public static readonly BindableProperty QueryIconNameProperty;
  public static readonly BindableProperty QueryIconProperty;
  public static readonly BindableProperty QueryProperty;
  public static readonly BindableProperty SearchBoxVisibilityProperty;
  public static readonly BindableProperty ShowsResultsProperty;

  public ImageSource ClearIcon { get; set; }

  public string ClearIconHelpText { get; set; }

  public string ClearIconName { get; set; }

  public ICommand ClearPlaceholderCommand { get; set; }

  public object ClearPlaceholderCommandParameter { get; set; }

  public bool ClearPlaceholderEnabled { get; set; }

  public string ClearPlaceholderHelpText { get; set; }

  public ImageSource ClearPlaceholderIcon { get; set; }

  public string ClearPlaceholderName { get; set; }

  public ICommand Command { get; set; }

  public object CommandParameter { get; set; }

  public string DisplayMemberName { get; set; }

  public bool IsSearchEnabled { get; set; }

  public IEnumerable ItemsSource { get; set; }

  public DataTemplate ItemTemplate { get; set; }

  public string Placeholder { get; set; }

  public string Query { get; set; }

  public ImageSource QueryIcon { get; set; }

  public string QueryIconHelpText { get; set; }

  public string QueryIconName { get; set; }

  public SearchBoxVisiblity SearchBoxVisibility { get; set; }

  public bool ShowsResults { get; set; }

  protected virtual void OnClearPlaceholderClicked();

  protected virtual void OnItemSelected(object item);

  protected virtual void OnQueryChanged(string oldValue, string newValue);

  protected virtual void OnQueryConfirmed();
}

Properties:

API Description
ClearIconHelpText The accessible help text for the clear icon
ClearIconNameProperty The name of the clear icon for usage with screen readers
ClearIcon The icon displayed to clear the contents of the search box.
ClearPlaceholderCommandParameter The paramter for the ClearPlaceholderCommand
ClearPlacehodlerCommand The command to execute when the ClearPlaceholder icon is tapped
ClearPlaceholderEnabled The enabled state of the ClearPlaceholderIcon. Default is true.
ClearPlaceholderHelpText The accessible help text for the clear placeholder icon
ClearPlaceholderIcon The icon displayed in the ClearIcon location when the search box is empty.
ClearPlaceholderName The name of the clear placeholder icon for usage with screen readers
CommandParameter The parameter for the Command
Command The ICommand to execute when a query is confirmed
DisplayMemberPath The name or path of the property that is displayed for each the data item in the ItemsSource.
IsSearchEnabled Controls the enabled state of the search box.
ItemsSource A collection of items to display in the suggestion area.
ItemTemplate A template for the items displayed in the suggestion area.
Placeholder A string to display when the search box is empty.
QueryIconHelpTextProperty The accessible help text for the query icon
QueryIconNameProperty The name of the query icon for usage with screen readers
QueryIcon The icon used to indicate to the user that search is available
Query The current string in the search box.
SearchBoxVisibility Defines the visibility of the search box in the Shells chrome.
ShowsResults Determines if search results should be expected on text entry

Protected Methods:

API Description
OnClearPlaceholderClicked Called whenever the ClearPlaceholder icon has been pressed.
OnItemSelected Called whenever a search result is pressed by the user
OnQueryConfirmed Called whenever the user pressed enter or confirms their entry in the search box.
OnQueryChanged Called when the Query is changed.

SearchBoxVisiblity

public enum SearchBoxVisiblity
{
  Hidden,
  Collapsable,
  Expanded
}
Value Description
Hidden The search box is not visible or accessible.
Collapsable The search box is hidden until the user performs an action to reveal it.
Expanded The search box is visible as a fully expanded entry.

BackButtonBehavior

public class BackButtonBehavior : BindableObject
{
  public ImageSource IconOverride { get; set; }
  public string TextOverride { get; set; }
  public ICommand Command { get; set; }
  public object CommandParameter { get; set; }
}
API Description
IconOverride Changes the Icon used for the back button.
TextOverride Changes the Text used to indicate backwards navigation if text is used.
Command Provides a replacement command to invoke when the back button is pressed.
CommandParameter The command parameter used with Command

FlyoutDisplayOptions

Determines how the ShellGroupItem should display in the FlyoutMenu.

  public enum FlyoutDisplayOptions
  {
    AsSingleItem,
    AsMultipleItems,
  }
Value Description
AsSingleItem The ShellGroupItem will be visible as a single entry in the Flyout.
AsMultipleItems The ShellGroupItem will be visible as a group of items, one for each child in the Flyout.

FlyoutHeaderBehavior

Controls the behavior of the FlyoutHeader when scrolling.

public enum FlyoutHeaderBehavior {
  Default,
  Fixed,
  Scroll,
  CollapseOnScroll,
}
Value Description
Default Platform default behavior.
Fixed Header remains visible and unchanged at all times
Scroll Header scrolls out of view as user scrolls menu
CollapseOnScroll Header collapses to title only as user scrolls

FlyoutBehavior

public enum FlyoutBehavior
{
  Disabled,
  Flyout,
  Locked
}
Value Description
Disabled The flyout cannot be acccessed by the user.
Flyout The Flyout is works as a normal flyout which can be open/closed by the user.
Locked The Flyout is locked out and cannot be closed by the user, it does not overalp content.

DataTemplateExtension

This extension quickly converts a type into a ControlTemplate. This is useful for instances where the template would otherwise be specified as

<ListView>
  <ListView.ItemTemplate>
    <DataTemplate>
      <local:MyCell />
    </DataTemplate>
  </ListView.ItemTemplate>
</ListView>

This can then be condensed down to

<ListView ItemTemplate="{DataTemplate local:MyCell}" />
public sealed class ControlTemplateExtension : IBindingExtension<ControlTemplate>
public sealed class DataTemplateExtension : IBindingExtension<DataTemplate>

Odds and Ends

What happens when you select a tab in Flyout which has been pushed to?

This is the equivalent of focusing the tab and calling PopToRoot on it.

What happens when I switch ShellItems and there are items on the backstack of a ShellContent of the old ShellItem

If the old ShellItem is templated, the back stack is lost. If the ShellItem is not templated the BackStack stays intact and when switching back to the old ShellItem the backstack will be properly reflected. Switching back may clear the backstack however as indicated in the above answer.

Effecient loading of pages

A major problem with using the Shell is the ease by which a user can end up loading all pages at the start of their application run. This large frontloaded allocation can result in quite poor startup performance if a large number of content pages are needed. In order to fix this templating should be employed when possible.

Templated ShellContents

Templating shell tab items is the most granular form of templating available, it is fortunately also the easiest to do. Take the following shell.

<?xml version="1.0" encoding="utf-8" ?>
<MaterialShell xmlns="http://xamarin.com/schemas/2014/forms"
               xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
               xmlns:local="clr-namespace:MyStore"
               x:Class="MyStore.Shell">

  <ShellItem Title="My apps &amp; games">
    <local:UpdatesPage />
    <local:InstalledPage />
    <local:LibraryPage />
  </ShellItem>

  <ShellItem GroupBehavior="ShowTabs">
    <local:HomePage />
    <local:GamesPage />
    <local:MoviesTVPage />
    <local:BooksPage />
    <local:MusicPage />
    <local:NewsstandPage />
  </ShellItem>
</MaterialShell>

When this Shell loads, all 9 pages will be inflated at once. This is because no templating is employed. To employ basic templating we can convert this into:

<?xml version="1.0" encoding="utf-8" ?>
<MaterialShell xmlns="http://xamarin.com/schemas/2014/forms"
               xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
               xmlns:local="clr-namespace:MyStore"
               x:Class="MyStore.Shell">

  <ShellItem Title="My apps &amp; games">
    <ShellContent Title="Updates"        Icon="updates.png" ContentTemplate="{DataTemplate local:UpdatesPage}" />
    <ShellContent Title="Installed Apps" Icon="apps.png"    ContentTemplate="{DataTemplate local:InstalledPage}" />
    <ShellContent Title="Library"        Icon="library.png" ContentTemplate="{DataTemplate local:LibraryPage}" />
  </ShellItem>

  <ShellItem GroupBehavior="ShowTabs">
    <ShellContent Title="Home"          Icon="updates.png"   ContentTemplate="{DataTemplate local:HomePage}" />
    <ShellContent Title="Games"         Icon="games.png"     ContentTemplate="{DataTemplate local:GamesPage}" />
    <ShellContent Title="Movies and TV" Icon="moviesTV.png"  ContentTemplate="{DataTemplate local:MoviesTVPage}" />
    <ShellContent Title="Books"         Icon="books.png"     ContentTemplate="{DataTemplate local:BooksPage}" />
    <ShellContent Title="Music"         Icon="music.png"     ContentTemplate="{DataTemplate local:MusicPage}" />
    <ShellContent Title="Newsstand"     Icon="newsstand.png" ContentTemplate="{DataTemplate local:NewsstandPage}" />
  </ShellItem>
</MaterialShell>

Pages are now only loaded as needed and can be unloaded as needed as well. If needed the ShellItem's themselves can also be templated with a collection and a DataTemplateSelector which prevents even the ShellContents from having to be loaded eagerly, however this is largely unneeded and templating ShellItem is more useful for Shells that have ShellItems with large numbers of otherwise similar tabs. Templating the ShellContent should be the key area to provide templating for performance concerns.

Reconstructing the Google Play Store User Interface

Please note this is not intended to be a demo of the best way to code an application, but really the most concise format to shove together the UI for GPS. It also makes no attempt to virtualize the relevant pages by using ViewModels and DataTemplateSelector's. This means this would be quite bad performing as all pages would be loaded at app start. Reader is warned.

All page content is assumed to be working right, this is only to get the general idea of the chrome.

Shell.xaml

A proper implementation of this would use ShellItems for every page and set the ItemsSource and ItemTemplate. This would allow each page to only be loaded once it is needed and unloaded when no longer needed.

<?xml version="1.0" encoding="utf-8" ?>
<MaterialShell xmlns="http://xamarin.com/schemas/2014/forms"
               xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
               xmlns:local="clr-namespace:MyStore"
               x:Class="MyStore.Shell"
               FlyoutHeaderBehavior="Fixed"
               FlyoutHeader="{x:Bind HeaderViewModel}">
  <MaterialShell.FlyoutHeaderTemplate>
    <local:CircleImageAndLabelControl HeightRequest="350" />
  </MaterialShell.FlyoutHeaderTempalte>

  <ShellItem Title="My apps &amp; games">
    <ShellItem.ShellAppearance>
      <MaterialShellAppearance NavBarCollapseStyle="Full">
    </ShellItem.ShellAppearance>
    <local:UpdatesPage />
    <local:InstalledPage />
    <local:LibraryPage />
  </ShellItem>

  <local:NotificationsPage Title="My notifications" />

  <local:SubscriptionsPage />

  <ShellItem GroupBehavior="ShowTabs">
    <ShellItem.ShellAppearance>
      <MaterialShellAppearance NavBarCollapseStyle="Full" TabBarCollapseStyle="Full" UseSwipeGesture="false">
    </ShellItem.ShellAppearance>
    <local:HomePage />
    <local:GamesPage />
    <ShellContent Title="Movies &amp; TV" Icon="moviesTV.png" ContentTemplate="{DataTemplate local:MoviesTVPage}">
      <ShellContent.MenuItems>
        <MenuItem Title="Open Movies &amp; TV app" Command="{xBind MoviesTVAppCommand}" />
      </ShellContent.MenuItems>
    </ShellContent>
    <ShellContent Title="Books" Icon="books.png" ContentTemplate="{DataTemplate local:BooksPage}">
      <ShellContent.MenuItems>
        <MenuItem Title="Open Books app" Command="{xBind BooksAppCommand}" />
      </ShellContent.MenuItems>
    </ShellContent>
    <ShellContent Title="Music" Icon="music.png" ContentTemplate="{DataTemplate local:MusicPage}">
      <ShellContent.MenuItems>
        <MenuItem Title="Open Music app" Command="{xBind MusicAppCommand}" />
      </ShellContent.MenuItems>
    </ShellContent>
    <ShellContent Title="Newsstand" Icon="newsstand.png" ContentTemplate="{DataTemplate local:NewsstandPage}">
      <ShellContent.MenuItems>
        <MenuItem Title="Open Newsstand app" Command="{xBind NewsstandAppCommand}" />
      </ShellContent.MenuItems>
  </ShellItem>

  <local:AccountPage />

  <MenuItem Title="Redeem" Icon="redeem.png" Command="{x:Bind RedeemCommand}" />

  <local:WishlistPage />

  <MenuItem Title="Play Protect" Icon="protect.png" Command="{x:Bind NavigateCommand}" CommandParameter="ProtectPage" />

  <MenuItem Title="Settings" Icon="settings.png" Command="{x:Bind SettingsCommand}" CommandParameter="SettingsPage" />

  <MaterialShell.MenuItems>
    <MenuItem Title="Help &amp; feedback" Command="{x:Bind NavigateCommand}" CommandParameter="HelpPage" />
    <MenuItem Title="Parent Guide" Command="{x:Bind NavigateCommand}" CommandParameter="ParentPage" />
    <MenuItem Title="Help &amp; feedback" Command="{x:Bind UrlCommand}" CommandParameter="http://support.google.com/whatever" />
  </MaterialShell.MenuItems>
</MaterialShell>

The store pages

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MyStore"
             x:Class="MyStore.HomePage"
             Title="Home"
             Icon="home.png"
             ShellAppearance.BackgroundColor="Green">
  <Label Text="Home content here" />
</ContentPage>
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MyStore"
             x:Class="MyStore.MoviesTVPage"
             Title="Movies &amp; TV"
             Icon="movies.png"
             ShellAppearance.BackgroundColor="Red">
  <Label Text="Movies and TV content here" />
</ContentPage>

And to add the search bar.

public class HomePage : ContentPage
{
  public class HomeSearchHandler : SearchHandler
  {
    public HomeSearchHandler ()
    {
      SearchBoxVisibility = SearchBoxVisibility.Expanded;
      IsSearchEnabled = true;
      Placeholder = "Google Play";
      CancelPlaceholderIcon = "microphone.png"
    }

    protected override void OnSearchConfirmed (string query)
    
{      // Navigate to search results page here
    }
    
    protected override void OnSearchChanged (string oldValue, string newValue)
    {
    }

    protected override void OnCancelPlaceholderPressed ()
    {
      // Trigger voice API here
    }
  }

  public HomePage
  {
    Shell.SetSearchHandler (this, new HomeSearchHandler ());
  }  
}

And so on and so on setting the colors and the content correctly.

For pages like the Settings page where the flyout should not be accessible until the back button is pressed:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MyStore"
             x:Class="MyStore.SettingsPage"
             Title="Settings"
             Icon="settings.png"
             ShellAppearance.BackgroundColor="Grey"
             MaterialShell.FlyoutBehavior="Disabled">
  <Label Text="Settings content here" />
</ContentPage>

TODO

  • Add API for search box
  • Add API for ContentSafeArea handling
  • Add API for floating menu
  • Add API for window menu items
  • Add API for snackbar
  • Add API for navigation interruption
  • Add API for bottom sheet
  • Add API to position FAB
  • Add stories to make navigation ideas clearer (partially done)
  • Add LeftBarButton style API
  • Add mechanism for getting Back Stack from ShellContent
  • Add API for changing color of the ribbon based on selected tab
  • Add optional suggestion support for SearchHandler
  • Add support for configuring always expanded search bar vs search bar as icon
  • Add API for getting the "Current" page from the MaterialShell class. Needed for some navigation scenarios.
  • Add API for saving "states" to the back stack
  • Add API to block flyout from coming out
  • Add API for "submenu" items for ShellContent's ala GPS -> Music -> Open Music app
  • Add API for CancelPlaceholder Command and Icon (usually used for microphone icon for voice search)
  • Segue API
  • Work out what to do with INavigation
  • Expand Bottom Sheet API to match Google Maps capabilities
  • API to handle Flyout presentation
  • API to disable Flyout gesture when needed
  • Large title API
  • Transitions API

Issues

ISearchHandler [FIXED]

There is a lot this interface is doing and worse it's probably going to need to be expanded as time goes on. Therefor it stands to reason that it should probably instead be an abstract base class the user can implement. This unfortunate means yet another object allocation. However it maintains flexibility in the future. It is likely this change will happen.

Floating Menu

The attachment API is a bit heavy and perhaps too confusing for users. Worse it might not map very well to all platforms to make sure the attachment works with animations. More work is required to validate this API.

@davidortinau
Copy link
Contributor

davidortinau commented Apr 12, 2018

Let's get this conversation humming, because we really need to hear from you, our community of developers!

FIRST, thanks @jassmith for laboring to capture this and craft a proposal for us to discuss in the open here.

I'm going to share some of my thoughts, ask some stupid questions, and ask some hopefully-not-too-leading questions. As the Program Manager for Xamarin.Forms I often have to ask questions in such a way that it makes it sounds like I don't have a clue (sometimes I don't), but really I just need to hear things from YOU without putting words in your mouth.

There are things I like in this proposal, because I can see how they might address some the problems I've been talking to many, many of you about, and we are working towards solving.

I also have reservations.

I'll kick off here with a couple general threads of thought about the Shell concept and the developer experience.

App Experience

to get a complete app experience that is properly structured, uses the right elements, with very little effort and a clear path to being good by default.

If I use this approach to describe my application at the top level, I get:

  • an app themed to look the same on every platform
  • material design (in this case) patterns on by default
  • the latest navigation patterns enabled with configuration
  • I don't have to labor to customize Entry or Button to look the same on iOS and Android and UWP?

Is that accurate? Other benefits I should highlight?

Instead of App I have MaterialShell. I need to learn/adopt a new top level application paradigm to benefit from this?

Once I'm into my app, I'm back in ContentPage land, correct? But all my Buttons and Entrys are material-fied and pretty. Can I still OnPlatform and the like to diverge the look (or behavior) to be different on another platform?

What's my migration path look like from an existing app to using this "shell"? I introduce the new MaterialShell, describe how I want my app to be structure and look, and just run it to see the new goodness?

Customizing

What are my options if I like the way the Flyout or the menu items look, but I need to tweak them to match my designers comps? At what point am I saying "Ugh, I should've rolled this all myself" and moving what I have to a standard Xamarin.Forms app structure?

If I have to totally abandon the MaterialShell, do I lose all that styling goodness and I'm back where I started (like today) with an Entry looking quite different between iOS and Android and UWP? I would like to not.

There's a tipping point I'm anticipating. After I've used this approach to get going quickly, I will hit some limitation and need to explore my options. What are they and at what point am I better off not usingMaterialShell?

The Problem

I'll close this first comment with a few questions to everyone reading.

  • Is the problem that this proposal attempts to address well defined?
  • Is it a problem you share?
  • As you read this spec, what problems that you have faced on previous projects do you feel this would solve for you?

@TonyHenrique
Copy link

TonyHenrique commented Apr 12, 2018

If possible, screenshots / design images would make even better.

Also check out this:

http://www.noesisengine.com/webgl/Samples.Buttons.html

@muhaym
Copy link

muhaym commented Apr 13, 2018

@jassmith It would have been great if the whole idea is presented as images. Kudos to the effort of putting the specifications up. My questions might be not correct.

My queries are

  • Tablet Support, Responsiveness of app adapting to different layout is not addressed.
  • Will the migration from the current model to the new model be challenging? From app developer perspective and Xamarin Forms contributors perspective.
  • I agree most of the app needs single UI (cause the amount of custom renderers we have to write sometime to achieve same UI concepts) without respecting platform specifics, will the new direction remove all the existing features, like will we still develop Xamarin.Forms apps with platform specific UI, or will everyone be forced to new Standards.
  • Will renderer concepts still exist inside MaterialShell
  • Can we actually say, in iOS, we want this to be rendered this way? Flutter has Cupertino Styles (apple UI) which works in Android, and Android UI which works in iOS. In What direction will Xamarin Forms move. Will developer have that kind of capablities which flutter offers now.

@ChaseFlorell
Copy link

ChaseFlorell commented Apr 13, 2018

Will this also include something akin to the TextInputLayout to support floating labels/placeholders as well as error messages? If yes, then I think Binding should be extended to include 'ValidateOnDataErrors` similar to wpf.

See my implementation of this here
https://github.com/XamFormsExtended/Xfx.Controls/blob/develop/src/Xfx.Controls/XfxBinding.cs

@ChaseFlorell
Copy link

Also, I wonder if MaterialShell should extend Shell so that one could create a HumanInterfaceShell for the iOS look and feel.

@dylanberry
Copy link

@ChaseFlorell that was going to be my comment as well. Material is great, but it's another thing if we want to write our own shells to suit particular UI needs.

@MelbourneDeveloper
Copy link

MelbourneDeveloper commented Apr 13, 2018

@davidortinau , @jassmith ,

Thanks for putting together this spec and allowing us to provide feedback.

Is the problem that this proposal attempts to address well defined?

Yes. The navigation system is not completely developed in Xamarin Forms so there is an open ended issue of how to complete it, or circumvent it altogether.

Is it a problem you share?

Yes.

As you read this spec, what problems that you have faced on previous projects do you feel this would solve for you?

I'm going answer with this question by saying that I don't think it will solve problems. Our particular problems are that the current navigation system is too rigid, not that it is not rigid enough. The clearest example for us is the TabbedPage. There is no TabbedView view, so if we want tabs in our app, we must use TabbedPage. So, the whole screen must be taken up by the TabbedPage and we cannot use real estate for own buttons, or other controls that we might want to put on the screen. My recommendation would have been to move more functionality out of Pages and down to Views rather than moving functionality in Pages up to yet again higher layer.

Things like FloatingMenu, and FlyoutBehavior scare me because they imply that navigation will be further hard coded in to the Xamarin.Forms system and further control will be taken away from the software developers. I can see some value in having a more standardized approach, but it will definitely come at a cost.

We have always worked in other XAML based technologies like Silverlight, WPF, and UWP. In those technologies, we have a much more open ended approach where we are able to define more of how the navigation works. Recently, we engaged a UI/UX consultant. He was unaware of XF's quirks. We just asked him to build us some screens based on the functionality of our software. Large swathes of what he recommended could not be implemented because of things like not having a TabbedView. I fear that instead of making navigation easier, what will happen is that this framework will actually make it harder to implement the designs that are given to us by UX/UI designers.

The other thing I would say is that this framework looks unprecedented in other XAML platforms, and I would say that the priority should be on providing standardization across the platforms rather than providing new frameworks that the other platforms won't be compatible with. We're currently building for three platforms now: Silverlight, Xamarin.Forms, and WPF. What we need is standardization across those platforms to create less deviation, not more.

@dylanberry ,

it's another thing if we want to write our own shells to suit particular UI needs.

Yes. This is my concern. Each app has its own particular use cases, and more rigidity is likely to make it harder - not easier to implement these.

There is also this concern: #2452 (comment)

@RichiCoder1
Copy link

RichiCoder1 commented Apr 13, 2018

I'll echo the above, as well as ask how gestures and pointers will function irt shape primitives?

@MelbourneDeveloper

This comment has been minimized.

@opcodewriter

This comment has been minimized.

@mackayn
Copy link

mackayn commented Apr 13, 2018

For me, it's been touched on but navigation is the single biggest stumbling block in Xamarin Forms and has caused me the most heartache over the last three years.

The navigation is not consistent across all three platforms with MasterDetail pattern causing many issues with the hamburger forcing you to push pages modal, you then have to implement a custom navigation bar as you can't add back but the animation looks awful on Android (even in MD).

  • Ideally you should be able to opt-out and override the content of the navigation bar with your own ContentView in many cases. PlatformSpecifics was originally mooted as something which would have allowed positioning of toolbaritems (way back when I was speaking to Bryan) but it transpired to be something of limited use.

  • Ability to override framework stuff like page popped animations

A lot of what's proposed seems very very useful, As long as you can just use material shell in specific pages and it isn't app wide it code be really useful. It's certainly something we'd use because at current I keep saying "It's a limitation of Xamarin Forms" to our UX guy a lot (as I did in my previous role). Dare I say it, we should get the feedback from a UX perspective who have previously styled Forms apps. It's should be opt-in like XamlC.

@mackayn
Copy link

mackayn commented Apr 13, 2018

Not sure about the name though, FlexShell would make more sense...but we now have Flexbox (even though we never asked for it...sorry David, couldn't help myself 😄)

Also, would this mean themes are kind of dead? never used them anyway.

@opcodewriter

This comment has been minimized.

@mackayn

This comment has been minimized.

@mhmd-azeez
Copy link

@jassmith does this mean that XF will take the Flutter approach (using a rendering engine instead of native controls) for the Shell?

@migueldeicaza
Copy link
Contributor

Folks, yesterday Jason and myself had a discussion on how to improve this spec and we will be doing a large update, we are splitting this into different issues.

@ddotlic

This comment has been minimized.

@opcodewriter

This comment has been minimized.

@jassmith
Copy link
Author

jassmith commented Apr 13, 2018

@TonyHenrique Yeah pictures would be great. I unfortunately am not an artist and do not own the rights to the images I am using for my own reference. I am hoping some design team member will have time to help me produce proper images for the spec.

@muhaym

  • Tablet support. It is intended to make sure the layout is adaptive to tablets. This is not going to cover cases for layouting inside your content pages of course, that would be a different feature. Largely I would handle that with the new VSM.
  • Mostly just putting your ContentPages in the right place and adapting to the new navigation concepts. I wont say it will always be trivial but it shouldn't be debilitating either.
  • Absolutely will not remove any features. In fact I am working on updating the spec to include a Shell base class that misses some of the features of MaterialShell but is otherwise a platform specific UI version of Shell.
  • Yes. Adding a Drawing just changes which renderer you get to something that known how to handle a drawing.
  • Yes. You can swap out which renderer gets used at any level of the hierarchy. The renderers are just changed by templates being set with special keys in resource dictionaries. You can change these templates to whatever you want or disable them by setting null to them.

@ChaseFlorell no, but Im open to suggestions if you want to attach an amendment to the spec. As for the Shell vs MaterialShell thing, I addressed that above. Step 1 was making sure MaterialShell made sense then breaking it out to a base class is step 2.

@dylanberry the base class wont really make that much easier. Its not as simple as changing DrawingTemplates. I intend to implement the MaterialShell aspect of this in platform specific code for optimal performance.

@RichiCoder1 gestures will be coming in here but the gist is the views with subregion gestures are coming as part of the CommandableSpan API and it will be expanded to support drawings with gestures as well. However in general it would be advised NOT to use gestures and let the native backends handle input. This is unrelated to the MaterialShell spec at this point now and belongs on the Drawing spec where I am happy to go into more detail. The long and short is the reason DrawingTemplate is a thing is we may include a SliderDrawingTemplate which has multiple Drawings that the renderer knows how to compose a slider out of and allows the input handling to continue to be handled in the native backend. This does not mean you would be forced to do this, it would simply be an optional thing to make the renderer fast as possible.

@mackayn a Transitions/Segue API is coming and should land in here soon. I am still working out some of my initial proposal naming. It will certainly be something that comes in phases with only Page Transistions being in phase 1. That will let you override framework animations however. As for opt-in. The Shell must be the root of your application and cannot nest anything but TemplatedPage's. That said the internal control theming is 100% in your control. Its little more than a magic switch to turn on/off the new templates and you can control that switch the same way we will. This lets you opt in and out of the theming at the page, the layout, or even the control level.

@Encrypt0r not exactly. Think of it as a hybrid. It will allow for rendering but under teh hood its all platform specific controls just being themed with drawing code. However there is an escape hatch to Skia that could be easily added (though I doubt it makes it anywhere near a v1).

@opcodewriter ListView2 (obviously it wont actually be named that) is on the roadmap this year at long last. Why a new ListView? Well the original API leads to a nearly limitless number of bugs and issues that we can't really effectively fix without totally breaking backwards compatibility. Things like Cell/ViewCell and ItemsView and the TemplatedItemsList while they worked well for what they were meant for a 1.0, are nowhere near capable of what they need to be for the modern usage of the API.

Unfortunately I cannot think of any way to resolve this within the ListView type itself, and I don't think the community has been able to either unfortunately. The best option right now is to keep ListView as it is, and then make something far sleeker with less overhead, less bookkeeping, less odd types that don't need to exist and trust the target frameworks to do what they do best.

Simply removing the old recycling strategy, which would be a huge backcompat breaking change we can't do, would remove a sizeable portion of the Xamarin.Forms codebase. ListView2 will bring you much closer to the metal by removing these items which were intended to be guard rails (or in the case of Cell, the unholy merger of 2 different internal projects) and are now just hurting everyone.

The old caching strategy impacts EVERY renderer in existence today. Its the reason why every renderer supports having its Element changed. If that went away by my best estimate about 10% of all code in the project goes away. It's really the biggest cancer of the project.

@jassmith
Copy link
Author

@davidortinau you get your own comment just because yours has so much lovely formatting!

If I use this approach to describe my application at the top level, I get:

  • an app themed to look the same on every platform
  • material design (in this case) patterns on by default
  • the latest navigation patterns enabled with configuration
  • I don't have to labor to customize Entry or Button to look the same on iOS and Android and UWP?

Is that accurate? Other benefits I should highlight?

Yes that is correct. There are lots of other benefits you can highlight but they should become obvious once you start messing with it. Like you can do bottom sheets, have a FAB, navigate using URLs (update still coming) and much much more.

Instead of App I have MaterialShell. I need to learn/adopt a new top level application paradigm to benefit from this?

Wrong. Your apps MainPage is the MaterialShell. Application is still your app root.

Once I'm into my app, I'm back in ContentPage land, correct? But all my Buttons and Entrys are material-fied and pretty.

With the exception to where you were wrong above, yes this is correct.

Can I still OnPlatform and the like to diverge the look (or behavior) to be different on another platform?

Yes.

What's my migration path look like from an existing app to using this "shell"?

Look at the Google Play Store repro case, imagine putting all your apps current pages in there. This only replaces those areas in your app where you are directly using Nav/Tab/MD pages. It doesn't have any impact on your ContentPages.

I introduce the new MaterialShell, describe how I want my app to be structure and look, and just run it to see the new goodness?

Bingo

What are my options if I like the way the Flyout or the menu items look, but I need to tweak them to match my designers comps?

You fully control the header, how the header collapses and hides. You fully control the look/feel of each of the "Cells" (they wont be cells) in the flyout. You fully control the header for each group in there as well. In effect you control the "look" of everything, however we still need to add a couple additional places for you to place extra content.

At what point am I saying "Ugh, I should've rolled this all myself" and moving what I have to a standard Xamarin.Forms app structure?

If the Google Play Store physical layout flyout looks completely different from what you want. Not marginally different, completely different.

If I have to totally abandon the MaterialShell, do I lose all that styling goodness and I'm back where I started (like today) with an Entry looking quite different between iOS and Android and UWP? I would like to not.

No MaterialShell is just setting some default resources so its children get them. You will be able to do that yourself. You might have to do it anyway, we haven't actually committed to making MaterialShell do that by default. If we do make it opt-in instead of opt-out, it will be a single API call for any subtree.

There's a tipping point I'm anticipating. After I've used this approach to get going quickly, I will hit some limitation and need to explore my options. What are they and at what point am I better off not using MaterialShell?

The intention is you are NEVER better off going non-Shell. You might not want Material but you should always want Shell (again the Shell base class is coming). Why? The look/feel of shell will be much more configurable, the navigation story will be much more unified, there should be nothing sane you can do with the other pages that you can't do with Shell.

Better, the intention is to make sure the Shell renderers are actually easily configured and sane. No magic hidden classes, not custom native view subclasses that we take hard deps on. As much as is possible every component of the renderers will be composed and swappable. That way if it doesn't work how you want it to, you can actually fix it...

Why didn't we do that at first? It wasn't a design goal in the early days and then we just ended up married to that... This is big enough and new enough that we dont have to carry that mistake with us.

@MelbourneDeveloper

This comment has been minimized.

@DanielCauser
Copy link

Would this be the new default behavior/way to develop in xamarin forms? Or would we still have an option to build our apps with each platform specific look and feel?

@jassmith
Copy link
Author

@DanielCauser while I suspect in the long run this may become the "default" way, this will in no way replace the current way. It will simply be a more integrated and modern way which will provide a lot more of the shell people are currently building by hand by default. On top of that since we will be providing the shell as a single control we can optimize a lot about how its drawn and laid out. No longer will you have 3 drawing passes just for your shell.

@programmation

This comment has been minimized.

@opcodewriter

This comment has been minimized.

@Elashi
Copy link
Contributor

Elashi commented May 5, 2019

@PureWeen I was hoping to use this idea :) .
Are you suggesting that this idea is not implemented yet?
If it is not implemented yet, I might be interested in working on it.

@dbwelch
Copy link

dbwelch commented May 5, 2019 via email

@PureWeen
Copy link
Contributor

PureWeen commented May 8, 2019

@Elashi it is not but I could see that being a really powerful feature for MVVM Scenarios

If you want to work on it can you create an issue with the basics of what you're thinking?

@jamesmcmenamin
Copy link

So from trial and error... Even though it has comments about gestures in shell 3.2 and its in TheLittlePlayground I cannot for the life of me make Gestures work on ANDROID with the visual package.

Am I missing something in the notes that Shell + gestures only work with iphone?

@TheBaileyBrew
Copy link

@davidortinau I know this is just some specs, and it's been closed for a while, but I was hoping you might be able to point me in the right direction since the specs point out that the below task is either completed or on the docket for development:

  • Add API for "submenu" items for ShellContent's ala GPS -> Music -> Open Music app

Currently since I can't get GroupHeaders to work, I'm looking to rework my FlyoutMenu to sort into 6 main groups, and then have a submenu appear full of FlyoutItems that navigate me to the routing I've predetermined.

My use-case is that I have 50+ options to display and putting that in a scrolling state is not UI friendly, but each option to be displayed is something that I need to allow my users to get to efficiently without having to scroll endlessly. Sorting into groups based on the over-arching theme of each option makes the most sense from a UI/UX standpoint.

Can you shed some light on where that stands in terms of development / production? - or point me in the direction of a code base that implements it so I can learn? (I've only been with Xamarin for 1 month, so I'm still new to some of the resources available).

@anthcool
Copy link

anthcool commented May 23, 2019

@TheBaileyBrew

I just Googled this repo with something that could work for your scenario most likely. It won't be Shell, but you could use it in a MasterDetailPage setup probably.

https://github.com/ishrakland/ListViewWithSubListView/

Also, they just moved and updated a lot of their course material to MSFT Learn (that I'm running through myself to fill in the gaps). That's found here: https://docs.microsoft.com/en-us/learn/browse/?products=xamarin

I'd try and run through the above. Best of luck and welcome aboard!

@BeleShew
Copy link

BeleShew commented May 23, 2019 via email

@PWaliaDev
Copy link

Hey Guys, i want to add tabs on basis of condition so how can i add tabs in Shell using C# not in Xaml as well how to add menu itms.

@PureWeen
Copy link
Contributor

@PureWeen
Copy link
Contributor

@BeleShew your question is probably better suited for stackoverflow or over at forums.xamarin.com

@PureWeen
Copy link
Contributor

@PWaliaDev you would do that through the various Items collections that are part of shell

Shell.Items.Add(new ShellItem())
new ShellItem().Items.add(new ShellSection())
new ShellSection().Items.Add(new ShellContent())

Though I'm assuming if we fixed this issue
#5428

that would suit your scenario?

@TheBaileyBrew
Copy link

@PureWeen -- I've got the display options set per your link, but what I'm struggling with is closer to what @BeleShew is dealing with (creating/removing flyout items on a per user basis)

Fixing #5428 would fit what I'm looking to achieve.

My app has users with a multitude of roles/access and I need to programmatically show menu options only to those users who are authorized (I don't want users to see what they can't do, only what they can.

Although I've been playing around with this, trying to figure out if it's possible to inspect my menu options and add rather then hide like this code does (but I'd have to iterate against every possible route and menu option which would eat up load time:

foreach (var item in this.Items)
        {
            if (item is FlyoutItem)
            {
                var removeSections = new List<ShellSection>();
                var fi = item as FlyoutItem;
                foreach (var child in fi.Items)
                {
                    if (child.Route == "NOT_AUTHORIZED_KEY")
                        removeSections.Add(child);
                }
                foreach (var s in removeSections)
                    fi.Items.Remove(s);
            }
        }

@Im-PJ
Copy link

Im-PJ commented Jun 26, 2019

Shell is great in theory but not in practical terms without these must have features.

  1. Add/remove shell items dynamically based on user role
  2. Login/Logout Scenario or two separate navigation route based on conditions.
  3. Optionally, Support for other MVVM frameworks like Prism

Can't use Shell in Business oriented apps until feature 1 and 2 are provided.

@dotMorten
Copy link
Contributor

@Im-PJ Why do you think 1 and 2 aren't possible? (pretty sure they are)

@Dresel
Copy link
Contributor

Dresel commented Jun 26, 2019

3 is in progress and tracked here.

@InquisitorJax
Copy link

Does anyone know if it's possible to get a tap event from an App Shell tab?
related forum question: https://forums.xamarin.com/discussion/159748/app-shell-pop-to-root-on-tab-tap

@dbwelch
Copy link

dbwelch commented Jun 26, 2019

Completely agree with @Im-PJ, and don’t understand how these features are not specifically provided for. It’s a main reason for a tool like Shell. Anyone can build a slide out menu!

@dotMorten maybe you can do 1 or 2 through extensive C# and hacking, but there are NO examples or mention of binding/disabling/hiding/adding Shell items at runtime that I can find. Seems that dynamic capabilities would have been a major goal of this tool. Creating an XML representation, along with some pathing features, is bare minimum, enough to give to the marketing folks, but not sufficient to actually use in a real, full featured mobile app.

@PureWeen
Copy link
Contributor

@Im-PJ

can you expand a little bit on how these are different?

Add/remove shell items dynamically based on user role
Login/Logout Scenario

Currently you can do a Login/Logout Scenario by using a TabBar or hiding the flyout navigation if you are on a login page

<TabBar Route="LoggedOut">
<LoginPage>
</TabBar>

<FlyoutItem Route="LoggedIn"></FlyoutItem>
<FlyoutItem></FlyoutItem>
<FlyoutItem></FlyoutItem>
<FlyoutItem></FlyoutItem>

If you're on the Login Page the only way to navigate away is through code.

Work is being done here to expose an IsVisible bindable property
https://github.com/xamarin/Xamarin.Forms/tree/shell_isvisible

It just needs a little refining before creating a PR

two separate navigation route based on conditions.

Can you provide examples of what you are trying to do and cannot do currently?

@thuyetpv
Copy link

hoi1
I'm using the Xamarin.Form Shell

Link Sample: https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/shell/flyout

Please help me. How to show the Tabbar in the About page?

@matteopiccioni
Copy link

I need To have two shell pages at the same time.
One for a flayout menu with page as home, settings, logout, etc
One for tabbedpages that should be pushed from home
For now I have to use the 'old way'

@thuyetpv
Copy link

thuyetpv commented May 6, 2020

I need To have two shell pages at the same time.
One for a flayout menu with page as home, settings, logout, etc
One for tabbedpages that should be pushed from home
For now I have to use the 'old way'

If using Tabbedpage, can't show Flyout . I want to show Flyout and Tabbar on this time.

I put all the pages in the so that the pages display the Tabbar, But I want to only have 4 elements in the tabbar (don't want to show More Tab). How do to do?

<shell>
<flyoutitem route="animals" flyoutdisplayoptions="AsMultipleItems">
<shellcontent route="cats"/>
<shellcontent route="dogs"/>
<shellcontent route="monkeys"/>
<shellcontent route="elephants"/>
<shellcontent route="bears"/>
<shellcontent route="about"/>
</flyoutitem>
</shell>

@matteopiccioni
Copy link

I need To have two shell pages at the same time.
One for a flayout menu with page as home, settings, logout, etc
One for tabbedpages that should be pushed from home
For now I have to use the 'old way'

If using Tabbedpage, can't show Flyout . I want to show Flyout and Tabbar on this time.

I put all the pages in the so that the pages display the Tabbar, But I want to only have 4 elements in the tabbar (don't want to show More Tab). How do to do?

<shell>
<flyoutitem route="animals" flyoutdisplayoptions="AsMultipleItems">
<shellcontent route="cats"/>
<shellcontent route="dogs"/>
<shellcontent route="monkeys"/>
<shellcontent route="elephants"/>
<shellcontent route="bears"/>
<shellcontent route="about"/>
</flyoutitem>
</shell>

Unfortunately I cant use Shell
I still have MasterDetailPage and TabbedPage in the same app (in iOS I have top tabbed pages using Naxam.TopTabbedPage.Forms plugin)
This was my old ticket

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.