Thursday, January 10, 2019

Xamarin Forms: Lazy load tabs in TabbedPage

In Xamarin Forms, we sometimes need the TabbedPage to display a tab layout. You can checkout the great official documentation here about how to create and use a TabbedPage.

Problem: Every tab is a Page instance


The TabbedPage is a multi page container where every tab is a Page object. No matter how you create the tabs (either by setting the TabbedPage.Children page collection with Page instances or by setting TabbedPage.ItemsSource and TabbedPage.ItemTemplate) the TabbedPage needs a Page instance for every tab and uses the Page.Title property to display the text header on the tab button.

So if we have 2 tabs, we will need to have 2 Page objects. Creating and adding several pages at once can be a problem if some of the pages have a complex layout or do heavier tasks (display several images for example) and it could slow down rendering the TabbedPage. And if the TabbedPage is the start page of the app, the app will appear to start slow:

<TabbedPage>
    <ContentPage Title="First tab">
        <Label Text="First tab layout" />
    </ContentPage>

    <ContentPage Title="Second tab">
         <!--Complex and slow to render layout here -->
          …               
    </ContentPage>
</TabbedPage>
In the example above, assuming the second tab has a complex layout, it can slow down noticeably navigating to this TabbedPage, even if the TabbedPage has the first tab as the selected tab at start.

Solution: Lazy load the tab Page content


A solution is to make the heavy pages load their content in a lazy manner, only when their tab becomes selected. This way, since these pages are now empty when TabbedPage is created, navigating to the TabbedPage suddenly becomes very fast!

To implement this mechanism, we need to:
  1. Detect the tab selection in the TabbedPage
  2. Ask the selected Page to load its content
  3. Have a way for the Page to load its content on demand
Obviously, it makes sense to use this mechanism on any tab Page other than the first one, which is the default selected page. If the first tab page is the heaviest, it might be still worth to apply the mechanism on the other pages.

For the first two tasks, I created a behavior for the TabbedPage page, called ActivePageTabbedPageBehavior. This behavior subscribes to the CurrentPageChanged event of the attached TabbedPage and when it receives the event, if the selected Page implements the custom interface IActiveAware, it sets its IsActive property to true:

class ActivePageTabbedPageBehavior : Behavior<TabbedPage>
{
    protected override void OnAttachedTo(TabbedPage tabbedPage)
    {
        base.OnAttachedTo(tabbedPage);
        tabbedPage.CurrentPageChanged += OnTabbedPageCurrentPageChanged;
    }

    protected override void OnDetachingFrom(TabbedPage tabbedPage)
    {
        base.OnDetachingFrom(tabbedPage);
        tabbedPage.CurrentPageChanged -= OnTabbedPageCurrentPageChanged;
    }

    private void OnTabbedPageCurrentPageChanged(object sender, EventArgs e)
    {
        var tabbedPage = (TabbedPage)sender;

        // Deactivate previously selected page
        IActiveAware prevActiveAwarePage = tabbedPage.Children.OfType<IActiveAware>()
            .FirstOrDefault(c => c.IsActive && tabbedPage.CurrentPage != c);
        if (prevActiveAwarePage != null)
        {
            prevActiveAwarePage.IsActive = false;
        }

        // Activate selected page
        if (tabbedPage.CurrentPage is IActiveAware activeAwarePage)
        {
            activeAwarePage.IsActive = true;
        }
    }
}

The IActiveAware interface is very simple:

interface IActiveAware
{
    bool IsActive { get; set; }
    event EventHandler IsActiveChanged;
}

Behaviors are a great mechanism to reuse functionality in a composable way. The view activation pattern using IActiveAware above can be created for other page containers as well. How exactly the IActiveAware target (a Page in this case) is activated can differ, and this is what the TabbedPage behavior above encapsulates.

Next, we need to make the tab page activation aware and implement the last task, to load content in the selected tab page. For this, I created a base generic abstract class called LoadContentOnActivateBehavior which does two things:
  1. It starts listening to the IsActiveChanged event of the attached IActiveAware instance
  2. When IsActiveChanged event is received, the behavior creates a view from its ContentTemplate property and sets the view as content to the attached VisualElement
abstract class LoadContentOnActivateBehavior<TActivateAwareElement> : Behavior<TActivateAwareElement>
    where TActivateAwareElement : VisualElement
{
    public DataTemplate ContentTemplate { get; set; }

    protected override void OnAttachedTo(TActivateAwareElement element)
    {
        base.OnAttachedTo(element);
        (element as IActiveAware).IsActiveChanged += OnIsActiveChanged;
    }

    protected override void OnDetachingFrom(TActivateAwareElement element)
    {
        (element as IActiveAware).IsActiveChanged -= OnIsActiveChanged;
        base.OnDetachingFrom(element);
    }

    void OnIsActiveChanged(object sender, EventArgs e)
    {
        var element = (TActivateAwareElement)sender;
        element.Behaviors.Remove(this);
        SetContent(element, (View)ContentTemplate.CreateContent());
    }

    protected abstract void SetContent(TActivateAwareElement element, View contentView);
}

The specialized LazyContentPageBehavior below knows how to actually set the content on the ContentPage:

class LazyContentPageBehavior : LoadContentOnActivateBehavior<ContentView>
{
    protected override void SetContent(ContentView element, View contentView)
    {
        element.Content = contentView;
    }
}

Having all the above, we can now go from the initial TabbedPage setup:

<TabbedPage ...>
    <ContentPage Title="First tab">
        <Label Text="First tab layout" />
    </ContentPage>

    <ContentPage Title="Second tab">
         <!—-Complex and slow to render layout here –->
          …               
    </ContentPage>
</TabbedPage>

to this setup:

<TabbedPage.Behaviors>
     <local:ActivePageTabbedPageBehavior />
</TabbedPage.Behaviors>

<ContentPage Title="First tab">
     <Label Text="First tab layout" />
</ContentPage>

<local:LazyLoadedContentPage Title="Second tab">
     <ContentPage.Behaviors>
         <local:LazyContentPageBehavior ContentTemplate="{StaticResource ContentTemplate}" />
     </ContentPage.Behaviors>

     <ContentPage.Resources>
         <ResourceDictionary>
             <DataTemplate x:Key="ContentTemplate">
                 <!-- Complex and slow to render layout –>
                 …
             </DataTemplate>
         </ResourceDictionary>
     </ContentPage.Resources>
</local:LazyLoadedContentPage>

What happened is we moved the ContentPage complex layout to become a DataTemplate.  Here's the custom LazyLoadedContentPage page which is activation aware:

class LazyLoadedContentPage : ContentPage, IActiveAware
{
    public event EventHandler IsActiveChanged;

    bool _isActive;
    public bool IsActive
    {
        get => _isActive;
        set
        {
            if (_isActive != value)
            {
                _isActive = value;
                IsActiveChanged?.Invoke(this, EventArgs.Empty);
            }
        }
    }
}

When the LazyLoadedContentPage is activated (it becomes the current page selected in the TabbedPage), the LazyContentPageBehavior behavior (which is attached to the page) creates the page content from the DataTemplate instance.

Does view activation sound familiar?


If you’re using Prism for Xamarin Forms, Prism already has a similar mechanism, it has the IActiveAware interface and a TabbedPage behavior similar to the ActivePageTabbedPageBehavior above in order to ”activate” an activation aware page when it becomes selected in the TabbedPage. 

However, Prism doesn’t have a similar LazyContentPageBehavior behavior, so you can use my implementation.

Demo app


You can find a demo app with this mechanism and complete source code in my repo here: https://github.com/andreinitescu/TabbedPageLazyLoadApp

The app mimics a complex layout by blocking the main thread for few seconds, something you should NEVER do in a real app. Also, hopefully, you will never have a layout which takes 5 seconds to load 😃

    public partial class SlowContentView : ContentView
    {
        public SlowContentView()
        {
            InitializeComponent();

            // Simulating a complex view
            // NEVER do this in real code
            Task.Delay(TimeSpan.FromSeconds(5)).Wait();
        }
    }

Wednesday, October 17, 2018

Too much fun with Xamarin Forms

I had a blast playing with Xamarin Forms and SVGs which end up with the following which works on iOS, Android and Windows UWP, checkout the videos for Android and iOS apps:




Credit for original design idea goes to Mikael Ainalem:

The SVG is created dynamically, based on the position of the text-boxes and the button, so it will work on any number of text-boxes and buttons. The actual SVG for the login screen in the videos, looks like this:



Friday, September 04, 2015

Debugging HTML5 running in iOS from Windows with Chrome dev tools!


In order to debug HTML5 running in Safari on iOS, I used to connect the iOS device to my Mac, open Safari, go to Developer menu(needs to be activated from Safari Preferences) and open the page.
You can find detailed steps how to do this on internet.

I tried to remote debug HTML5 with other tools (Telerik has one), but the best way is with an utility called ios-webkit-debug-proxy which uses Chrome dev tools!

You can find it here https://github.com/google/ios-webkit-debug-proxy and its Windows port: https://github.com/artygus/ios-webkit-debug-proxy-win32

It allows you inspect the WebKit (either the Safari or UIWebViews) running on the iOS device from your Windows machine with Chrome dev tools.
This utility connects to the WebKit for inspection.

Steps (most of these are written in https://github.com/google/ios-webkit-debug-proxy):
  1. Go to https://github.com/artygus/ios-webkit-debug-proxy-win32
  2. You can either get the compiled version or build from the source. I chose to built it from source just for the kicks.
  3. Run the ios-webkit-debug-proxy.exe
  4. In Chrome, go to http://localhost:9222/ to see web-browser tabs open in Safari or the WebViews on the iOS device. Click on a tab.
  5. A new window should open with Dev tools. Note the silver icon, click on it and choose to 'Load unsafe scripts':



Some things to keep in mind:

- note that a web server is being used. when inspecting a tab, the link is
  https://chrome-devtools-frontend.appspot.com/static/27.0.1453.93/devtools.html?host=localhost:9222&page=1
  not sure if this might be a security issue for what you're debugging.

- in the inspector window, clicking on elements in the Elements tab doesn't do anything; you can use arrow keys to navigate up and down in the DOM tree




Monday, June 22, 2015

GridSplitter control for Xamarin Forms

I created a GridSplitter control for Xamarin Forms, for iOS and Android.

You can find the full description of how it works and how to include it in your app:
https://github.com/andreinitescu/GridSplitterApp

Some screenshots with sample layouts included in the sample app:

mGkd879Oqv

and a Grid with both horizontal and vertical splitters:

DnaXEi1wzw

The sample app also shows a technique to create reusable custom controls which you can style easily just with XAML, very similar to how it works on Windows.

Wednesday, June 17, 2015

Easiest way to know which w3wp.exe PID corresponds to which application pool

I keep forgetting this.
Open Task Manager and have it show the Command Line column
You can see for the w3p.exe processes the name of the app pool in the command line parameters


Wednesday, June 10, 2015

Xamarin Forms: Create a style BasedOn default style defined in app's global resource dictionary

Suppose there’s a style defined in app's global resource dictionary (App.xaml):

<Style TargetType="Label">
    <Setter Property="TextColor" Value="Red" />
</Style>

And this style defined in a page:

<Style x:Key="MyLabelStyle" TargetType="Label">
    <Setter Property="FontSize" Value="14" />
</Style>

If you want MyLabelStyle to inherit the global style, one way is to use this syntax:

<Style x:Key="MyLabelStyle" TargetType="Label" BasedOn=”{StaticResource Xamarin.Forms.Label}”>

otherwise MyLabelStyle will not have the text color red.

Note that this won’t work:
<Style x:Key="MyLabelStyle" TargetType="Label" BasedOn=”{StaticResource {x:Type Label}}”>

Instead of hard coding the Label’s type full name, a nicer way would be to define a custom markup extension which resolves the Label type (something like this)

Xamarin Forms style resets

There are some default styles which you might want to reset in your Xamarin Forms apps.
For example, some container controls have default padding and spacing for their child views.

In a more complicated UI, sometimes these default styles can become an issue. Because the UI you build is complex, you can forget about these default values and you wonder why some views are not positioned the way you want.

I created a small XAML 'resets' snippet, which can be added to the App.xaml:
https://gist.github.com/andreinitescu/69e8afcad1ed9de69b76