Tuesday, May 30, 2017

Introducing ELXF: A UI Framework for Concise, Maintainable & Fast Programmatic UI's for Xamarin.Forms

ELXF is a new Xamarin.Forms UI framework that allows you to tap into the 2X speed increase possible with RelativeLayouts, while granting concise UI code, extra power, and improved maintainability.

Today I’m happy to announce a new UI framework for Xamarin.Forms. It’s called EasyLayout.Forms (ELXF) and is an alternative to XAML and to programmatic nested view creation. It's goals are:
    1. Maximize UI performance by reducing excess render cycles associated with traditional view nesting
    2. Increase maintainability and readability by removing ceremony and keeping layout code concise
    3. Simplify usage of RelativeLayout while increasing its power and abstracting away its quirks

    In this post I’ll briefly explain what it is, then get into why we need a new UI framework in the context of each of the above three goals. I'll finish with limitations, some history, and how to get started.

    What Is ELXF?

    EasyLayout.Forms (ELXF) is a C# Domain Specific Language that allows you to define the relationships between children in a RelativeLayout. For example to position one element 50px below another in XAML you would normally do this:

            RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToView, Property=X, ElementName=MainLabel, Constant=0}"
            RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView, Property=Y, ElementName=MainLabel, Constant=50}"
            RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView, Property=Width, ElementName=MainLabel}"
            RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToView, Property=Height, ElementName=MainLabel}"

    With ELXF you can do the same thing like this:

    _relativeLayout.ConstrainLayout(() =>
        _relativeLabel.Bounds.Top == _mainLabel.Bounds.Top + 50 &&
        _relativeLabel.Bounds.Left == _mainLabel.Bounds.Left &&
        _relativeLabel.Bounds.Width == _mainLabel.Bounds.Width &&
        _relativeLabel.Bounds.Height == _mainLabel.Bounds.Height)

    There's a handy self-documenting page that summerizes all of the options on github in LayoutExamplePage.cs.

    But what's wrong with the regular way of doing layouts? Why do we need a new framework?

    Maximize Performance

    Today, regardless of whether you choose to layout UI with XAML or programmatically, the path of least resistance is to create nested view layouts with several levels of StackLayout’s, Grid’s, TableView’s, and custom views.

    This creates a performance problem that Michael Ridland explains extremely well in his article Hacking the Xamarin.Forms Layout System for Fun and Profit. It’s worth reading the article a couple of times if you haven’t, but here is one of his key points:

    A child of a stacklayout will always cause a full layout cycle, there’s no way a StackLayout can short circuit this cycle.

    The solution is described on the Xamarin.Forms documentation on ListView performance:

    AbsoluteLayout has the potential to perform layouts without a single measure call. This makes it very powerful for performance. If AbsoluteLayout cannot be used, consider RelativeLayout.

    To better illustrate the problem and its solution I created two Xamarin Forms pages, one using view nesting, and one using a RelativeLayout with ELXF. The page simply shows some products (from Northwind) and you can tap one of them to select and then confirm the choice.

    The nested view version goes four levels deep on the header and three levels deep in the ListView.

    This may look complicated in a single image, but I honestly feel this is fairly typical if not actually simpler than what a real-world app might do.

    To compare the performance, I counted the number of measure and draw cycles for each label after performing the same set of steps on each version of the page (scroll, select 3 products, update text in all labels 3 times aka click calculator button 4 times). I then gave a score to each label based on roughly how expensive it was to draw, and set colors to show a heat map.

    Here's the traditional page:

    And here’s the RelativeLayout with ELXF version:

    The numbers in parenthesis are the number of measure operations and the number of draw operations. As you can see the 2nd one is roughly twice as fast.

    If you want to check these out yourself* the main pages are at: TraditionalPerformancePage.xaml and ElxfPerformancePage.cs and the custom views are in the Controls folder. There's a lot more to this topic such as the importance of fully constraining your views that I'll save for a later post.

    For now we've confirmed the Xamarin documentation and know RelativeLayout’s generally outperform nested views. But why not just use RelativeLayout’s in XAML or programmatically?

    * fyi there's currently an issue in Xamarin.Android 7.3.1 for Visual Studio users that causes RelativeLayouts in ListViews to load extremely slowly on Android. The current workaround is to build from a Mac.

    Increase Maintainability

    Consider the following example in XAML:

        <Label BackgroundColor="Aqua"
            Text="Main Label"
            RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent, Property=X, Constant=10}"
            RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent, Property=Y, Constant=10}"
            RelativeLayout.WidthConstraint="{ConstraintExpression Type=Constant, Constant=100}"
            RelativeLayout.HeightConstraint="{ConstraintExpression Type=Constant, Constant=40}"
        <Label BackgroundColor="OrangeRed"
            RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToView, Property=X, ElementName=MainLabel, Constant=110}"
            RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView, Property=Y, ElementName=MainLabel, Constant=50}"
            RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView, Property=Width, ElementName=MainLabel}"
            RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToView, Property=Height, ElementName=MainLabel}"

    This renders two labels like this:

    I don’t know about you, but I find that code very hard to read. The ELXF version of that looks like this:

    relativeLayout.ConstrainLayout(() =>
        _mainLabel.Bounds.Top == relativeLayout.Bounds.Top + 10 &&
        _mainLabel.Bounds.Left == relativeLayout.Bounds.Left + 10 &&
        _mainLabel.Bounds.Width == 150 &&
        _mainLabel.Bounds.Height == 40 &&

        _relativeLabel.Bounds.Top == _mainLabel.Bounds.Bottom + 10 &&
        _relativeLabel.Bounds.Left == _mainLabel.Bounds.Right + 10 &&
        _relativeLabel.Bounds.Width == _mainLabel.Bounds.Width &&
        _relativeLabel.Bounds.Height == _mainLabel.Bounds.Width

    It’s concise, powerful, and the syntax is always verified by the compiler. It also fixes a duplication problem in that XAML example. Can you spot the issue?

    Simplify RelativeLayout

    While Xamarin.Forms RelativeLayout’s aren’t exactly broken, they are far less powerful than iOS’s Autolayout or even Android’s RelativeLayout with it's fairly extensive set of LayoutParams. The good news is Xamarin realizes this and have plans to introduce a more powerful version in Xamarin.Forms 3. The problem today, however, is that they essentially only allow you to control the top left pixel.

    For example if you look again at the XAML above you’ll see that to align RelativeLabel to the right of MainLabel we had to add 110 (the width of MainLabel plus a margin) to RelativeLabel’s X. What we really want is an attribute like RelativeLayout.RightEdgeConstraint instead of the RelativeLayout.XConstraint attribute.

    As it stands if we ever change MainLabel’s width, we must remember to increment RelativeLabel’s XConstraint. That's the kind of duplication that hides bugs and complicates maintainability. However, even without ELXS we can do a little better.

    If we write this in code it looks like this:

        Constraint.RelativeToParent(rl => rl.X + 10),
        Constraint.RelativeToParent(rl => rl.Y + 10),

        Constraint.RelativeToView(mainLabel, (rl, v) => v.X + v.Width + 10),
        Constraint.RelativeToView(mainLabel, (rl, v) => v.Y + v.Height + 10),
        Constraint.RelativeToView(mainLabel, (rl, v) => v.Width),
        Constraint.RelativeToView(mainLabel, (rl, v) => v.Height)

    Better, right? RelativeLayouts in code have more power. So maybe we don't need a new framework after-all.

    Except, even if you don't agree that the ELXS version of

    _relativeLabel.Bounds.Left == _mainLabel.Bounds.Right + 10

    is easier on the eyes than

    Constraint.RelativeToView(mainLabel, (rl, v) => v.X + v.Width + 10)

    the code-behind version still has serious limitations when it comes to Centering elements.

    The Centering Problem

    Suppose we want to center-align a 3rd view under the 2nd one. If we attempt something like this:

            (rl, v) => v.X + (v.Width * .5f) – (centerLabel.Width * .5f)),
            (rl, v) => v.Y + v.Height)

    We’ll discover that it renders like this:

    Why didn’t CenterLabel pull further left? It’s because when the XConstraint lambda was evaluated, 'centerLabel' hadn’t been rendered yet. A non-rendered view gives a Width or Height of -1. The solution, documented nicely in this StackOverflow post, is this:

    Size GetSize(VisualElement ve, RelativeLayout rl) =>
        ve.Measure(rl.Width, rl.Height).Request;

            (rl, v) => v.X + (v.Width * .5f) - (GetSize(centerLabel, rl).Width * .5f)),
            (rl, v) => v.Y + v.Height + 10)

    That GetSize() local function (some C# 7 sugar) solves the problem by calculating what the width of centerLabel will be after it’s rendered. That renders nicely like this:

    While that works, perhaps you’ll agree that it's difficult to discern intent among all that math. A complex page with a lot of this style code is liable to hide bugs and obfuscate intent.

    Worse, it’s not always this easy. What if we want CenterLabel to have a width relative to MainLabel. If we do this:
            (rl, v) => v.X + (v.Width * .5f) - (GetSize(centerLabel, rl).Width / 2)),
            (rl, v) => v.Y + v.Height + 10),
            (rl, v) => v.Width)

    We end up with this:

    The problem is our GetSize() method is calculating the width of the label prior to any RelativeLayout width constraints.

    This is the point at which we’re stuck with the solution of hard-coding (duplicating) MainLabel’s width.

    ELXF to the Rescue

    EasyLayout.Forms can solve the centering problem. It translates LINQ expressions into Children.Add() calls with the correct parameters, it incorporates calls to a GetSize() type function when necessary, and in many cases it can solve the GetCenter() problem from above by searching back through prior LINQ expressions to determine what height or width the current element should be.
    The final solution turns into this:

    relativeLayout.ConstrainLayout(() =>
        _mainLabel.Bounds.Top == relativeLayout.Bounds.Top + 10 &&
        _mainLabel.Bounds.Left == relativeLayout.Bounds.Left + 10 &&
        _mainLabel.Bounds.Width == 150 &&
        _mainLabel.Bounds.Height == 40 &&

        _relativeLabel.Bounds.Top == _mainLabel.Bounds.Bottom + 10 &&
        _relativeLabel.Bounds.Left == _mainLabel.Bounds.Right + 10 &&
        _relativeLabel.Bounds.Width == _mainLabel.Bounds.Width &&
        _relativeLabel.Bounds.Height == _mainLabel.Bounds.Height &&

        _centerLabel.Bounds.GetCenterX() == _relativeLabel.Bounds.GetCenterX() &&
        _centerLabel.Bounds.Top == _relativeLabel.Bounds.Bottom + 10


    ELXF makes the RelativeLayout more powerful, but it can't patch over all of the issues. Until Xamarin.Forms 3 comes out, the following are a few of the known issues:
    • If you update the text of a view with Right or Center constraints, the relative layout doesn’t know to redraw it. To force the redraw you have to call relativeLayout.ForceLayout() twice
    • You can’t currently constrain a Left edge to one view and a Right edge to another view the way you could with iOS Autolayout. The workaround is to set the width just like you would with a regular RelativeLayout
    • Unlike the iOS version of EasyLayout, be aware that ELXF has no less than or greater than constraints

    A Brief History

    Speaking of the iOS version of EasyLayout, I must give credit where it's due and provide some context. EasyLayout is a UI framework originally developed by Frank Krueger (@praeclarum) to simplify doing programmatic Autolayout in Xamarin.iOS. It does this by creating a simple DSL using the awesome Expression Trees feature of C#. EasyLayout for iOS is so powerful that I wouldn’t start a Xamarin.iOS project without it, and I honestly feel sorry for traditional iOS developers for not having anything like it. But it was only for Xamarin iOS.

    Then, earlier this year, my team decided to move away from AXML files on our Xamarin.Android project. They did this because on our large project AXML files take a very long time to generate and significantly slow development. I took the opportunity to port EasyLayout to Android in the form of EasyLayout.Droid. This turned out to be a fantastic solution for our team, and I now wouldn’t do a Xamarin Android project without EasyLayout.Droid.

    While ELXF is not yet as mature as its predecessors, I did at least have the opportunity to bring in lessons learned from two mature projects.

    Getting Started

    If you'd like to give it a spin you can install it via nuget into a project with:

    Install-Package EasyLayout.Forms

    The source code is all at GitHub and there are an extensive set of examples in the source code like here and here. And as mentioned there is a self documenting page. Also if you clone source there is a playground page where you can experiment.

    What's nice is that if you like it you can adopt ELXF on your existing projects on a page by page basis. In fact, you can even use it for just a single view in a single relative layout. There's no obligation to adopt it everywhere. Just don't be surprised if, like me, you grow to like it enough to want it everywhere.


    If you end up using and liking EasyLayout.Forms please shoot me a note on twitter @lprichar, I'd love to hear from you.

    Wednesday, April 19, 2017

    Six Disastrous Mistakes for Cross-Platform Mobile Projects

    "I'm starting a cross-platform mobile project.  What problems should my team solve before we begin?"

    What an enlightened question, I thought.

    The individual standing next to me at a local developer conference had a software architecture background.  He clearly understood that laying a solid foundation at the outset of a project can either spell success, or result in project delays, massive technical debt, and quagmires for even rudimentary tasks.

    As a consultant of nearly two decades I've seen all too well the results of poor project planning.  After 36 individual projects, eight of which were mobile, four of which were cross-platform mobile, I felt comfortable answering the gentleman's question with plenty of first-hand knowledge to back it up.

    This post answers the question of what problems a mobile team should consider at project outset.  It's expressed in real world mistakes and the resulting consequences as I've witnessed them.

    1. Overemphasize One Platform

    One customer I knew built out a Xamarin Android app, then at some point copy-pasted 90% of the C# codebase into an iOS project.  Zero code reuse.

    It was a cute trick (not really).  It was also a disaster.

    Getting cross-platform architecture right is tricky.  And until you've built out every feature for both platforms you will not discover the weaknesses in your cross-platform architecture.  The longer you go before fixing those architectural weakness, the more time consuming and messy the solution will become, if it comes at all.

    Regardless of whether you, your team, or your product owner prefers one platform over another, do your project a favor and don't call any task done until it has been built out for all platforms.

    2. Specialize in a Platform

    On several projects I've seen "the iOS dev" write a screen for iOS and later "the Android dev" write the same screen again for Android.  The result is usually code duplication, limited code reuse, and, since each developer must get up to speed on and then solve issues specific to that screen, two times the amount of effort!

    Done right, building a mobile app for multiple platforms should be only slightly more effort than for one.  Maximize your investment in cross-platform development by encouraging developers to address the same problem on all platforms before moving on to the next task.

    3. Work at The Wrong Level of Abstraction

    On two mobile projects now I've witnessed firsthand the results of working at too low a level of abstraction.  Think going Xamarin without either Xamarin.Forms or an MVVM Framework like MvvmCross.  Think choosing PhoneGap/Cordova without something like Ionic.  

    The fail starts when a developer faces a common problem for which the framework doesn't account (like marshalling to the main thread from cross-platform code, dependency injection, or messaging between view models).  The developer manually implements a solution that has been solved many times over more generally by more robust frameworks.  Not a great use of time, but not terrible -- except it gets worse.

    Later, a second developer faces the same problem.  Not knowing of the 1st solution, they re-implemented it again, this time slightly differently.  Add code duplication to the sin of wasted effort.

    Later yet, a third developer facing the problem discovers the 1st solution.  However, being under a tight deadline they copy-paste it and make a couple of changes rather than taking the time to abstract it out the way a good framework would have to begin with.  Add creating a maintenance disaster to the list of sins.

    Keeping a low-level framework may sound lightweight, agile, and freeing from an individual contributor's perspective, but in my experience, it hurts the project in the long run.  Eventually it creates a fractured, bloated, technical debt laden codebase that the project will probably never recover from.  Plus, code written in month one will look drastically different from code written in month six, thus exacerbating the maintenance headache.

    For this reason, choosing a high-level framework at the outset, even one that may seem opinionated and bloated at first, is more likely to ensure a project's long term success.

    4. Postpone Cross-Platform Navigation

    Navigation has been problematic for every cross-platform project upon which I've worked, even when working at the correct level of abstraction.  A good framework goes a long way, but regardless be sure make sure you address the following topics:

    • Ensure navigation occurs in the ViewModel, or wherever shared code lives
    • Write at least one unit test that asserts a navigation has occurred, to confirm it's abstracted out correctly (and watch The Deep Synergy Between Testability and Good Design by Michael Feathers)
    • Account for sending parameters to destination pages as well as returning parameters from them
    • Consider modal dialogs, including whether they block until dismissed (they should)
    • Figure out how navigating to a cached page (such as a tab view) affects the page lifecycle
    • Determine how to navigate back via the API, how to modify the back stack, and how using the back stack will affect the page lifecycle

    5. Ignore Logging

    I once worked on a project where a developer had explicitly removed all logging.  He felt it was a performance problem.  The result: the app might have been infinitesimally faster, but diagnosing issues in the field was nearly impossible.  

    Even if you have a stack trace, you simply cannot solve harder problems like race conditions without good logging.  On mobile projects you can defer log persistence, but at the very least write an API with various log levels within the first week of starting a new project, and make sure everyone uses it.

    6. Defer Back-End Work

    A few years ago, I was brought in to work on a mobile project where 90% of the UI had been built out.  I was tasked with implementing data persistence.  No big deal, right?  Except retrofitting a SQLite database with offline support, server API's, a server database, and synchronization logic was a gargantuan effort.  The customer couldn't figure out why the project was taking so long to complete.  They'd essentially been sold a nice-looking car with no engine.

    This may sound extreme, but in my experience most mobile projects forget until the end to account for a variety of hidden, back end issues including:

    • Offline support (tested not just on launch, but wherever network failure may occur)
    • Data synchronization (e.g. data concurrency)
    • Progressive page loading (which is essential when considering)
    • Slow network conditions
    • Authentication and authorization
    • Loading animations (can cause a lot of problems if not done correctly)
    • Testing with lots of data (e.g. View Recycling is implemented and works correctly for all lists)
    • Ignoring memory management (e.g. unsubscribing from all events)

    Bonus Mistakes

    I excluded the following candidate mistakes because they aren't quite as critical to solve up front, but they're worth mentioning in passing:

    • Not unit testing
    • Not explicitly identifying all target devices and OS versions
    • Not routinely testing on physical devices
    • Leaving animations till last
    • Not solving Web API versioning (i.e. how to address breaking changes) 
    • Not having consistent, scalable, naming conventions (e.g. 'View/[area]/LoginViewController.cs')
    • Developing without realistic data
    • Ignoring how data migrations will work on subsequent deploys


    Hopefully you've found something on this list will jump start your next mobile project to long term success.  Or, if you have a classic mistake to share that I've missed, please write in the comments, or hit me up on Twitter @lprichar.