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

Conclusion


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.

Monday, March 13, 2017

Kill AXML - Programmatic ListViews in Xamarin Android

Displaying list data in Android using a custom layout is traditionally accomplished by inflating an AXML file for each row of data.  However, in my article introducing EasyLayout.Droid I made the case that AXML files slow development speed and decrease cross platform re-usability.

In this article, I'll show how to build custom Android ListView's 100% programmatically, and hopefully I'll convince you that the results are cleaner, more modular, and more maintainable.



Misguided Guidance?


Displaying a list of data in Android with one of the built-in row views, such as SimpleListItem1 for a single line of text or ActivityListItem for text plus an image, is well documented and fairly straightforward.

But to display list data using a custom layout, the Xamarin documentation makes it sound like you're best off using an AXML file:

The four built-in row views are very simple. To display more complex layouts (such as a list of emails, or tweets, or contact info) a custom view is required. Custom views are generally declared as AXML files in the Resources/Layout directory and then loaded using their resource Id by a custom adapter. The view can contain any number of display classes (such as TextViews, ImageViews and other controls) with custom colors, fonts and layout.

Then later in the documentation it sounds like you're required to use an AXML file:

Another AXML layout file is required to contain the custom layout for each row that will appear in the list view.

In fact, not only are AXML files optional, but if you follow the guidance in the documentation you'll end up with code that isn't SOLID.

All Other Ground is Sinking Sand


The documentation includes the following example that illustrates the best practice of view recycling, whereby an AXML file is only inflated when absolutely necessary (i.e. view == null), and the rest of the time the view is reused and subviews are extracted and updated.

public override View GetView(int position, View convertView, ViewGroup parent)
{
    var item = items[position];
    View view = convertView;
    if (view == null) // no view to re-use, create new
        view = context.LayoutInflater.Inflate(
            Resource.Layout.CustomView, null);
    view.FindViewById<TextView>(Resource.Id.Text1)
        .Text = item.Heading;
    view.FindViewById<TextView>(Resource.Id.Text2)
        .Text = item.SubHeading;
    view.FindViewById<ImageView>(Resource.Id.Image)
        .SetImageResource(item.ImageResourceId);
    return view;

}

I have a couple of problems with this code:

  1. Where would you add an event subscription if the view required a button?
  2. Where would you initialize the font size, font family, or color of a text view if you wanted to share the code between iOS and Android?
  3. How would you handle making subtle changes to the UI based on the type of item?

The correct answer to #1 and #2 is that all one-time setup code belongs inside the if statement.  If, for example, you were to put an event subscription along-side the FindViewById statements you would be in for a nasty surprise if you tapped the button after scrolling up and down the list a few times (let alone how and when would you unsubscribe).

The correct answer to #3 is that you'd have to put an additional if block outside of the 1st if statement, and in the process, violate the Open/Closed principle of SOLID.

The example also violates the Single Responsibility principle of SOLID in that it combines setup code that occurs infrequently and view recycling code that occurs quite frequently.  

So, while the code above looks innocuous enough, it encourages you down a bad path whereby you're liable to violate several tenants of SOLID. 

A World Without XML


I'm proposing keeping the view recycling -- except, instead of inflating the view from AXML, you'll instantiate a custom view.  Then, instead of pulling sub-views out of the inflated view and updating them with FindViewById() you'll just ask the view to update itself:

public override View GetView(int position, View view, ViewGroup parent)
{
    var productRowView = view as ProductRowView ??
        new ProductRowView(_context);
    var product = _items[position];
    productRowView.Update(product);
    return productRowView;

}

GetView() is now shorter and much cleaner because it has fewer responsibilities -- mostly determining when to instantiate a new view (not how to do it) or when to update a view (and not how to do that).

We've moved all remaining logic into a dedicated class called, in this example, ProductRowView.  Infrequent, one-time setup code is now the responsibility of ProductRowView's constructor, and frequent view recycling code is the responsibility of the Update() method.

Plus, we could now easily subclass ProductRowView if we had different display needs for different types of products (it's more SOLID).

I suspect Uncle Bob Martin would be very happy with this change.

Where's the Beef?


ProductRowView now looks fairly similar to any 100% programmatic custom view.  One difference is it inherits from RelativeLayout, so you can (optionally) use EasyLayout's .ConstrainLayout() call.

public class ProductRowView : RelativeLayout
{
    private TextView _titleText;
    private TextView _dollarText;
    private TextView _amountText;

    public ProductRowView(Context context) : base(context)
    {
        SetViewProperties();
        AddViews();
        ConstrainLayout(this);
    }

    private void SetViewProperties()
    {
        var height = ViewUtils.DpToPx(Context, 40);
        var width = ViewGroup.LayoutParams.MatchParent;
        LayoutParameters = new ViewGroup.LayoutParams(width, height);
    }

    private void AddViews()
    {
        _titleText = this.Add<TextView>();
        _dollarText = AddDollarText(this);
        _amountText = this.Add<TextView>();
    }

    private static TextView AddDollarText(ViewGroup parent)
    {
        var dollarText = parent.Add<TextView>();
        dollarText.Text = "$";
        dollarText.TextSize = 8;
        return dollarText;
    }

    private void ConstrainLayout(RelativeLayout relativeLayout)
    {
        relativeLayout.ConstrainLayout(() =>
            _titleText.Left == this.Left + 20
            && _titleText.Top == this.Top + 10
            && _titleText.Bottom == this.Bottom - 20

            && _amountText.Right == this.Right - 20
            && _amountText.Top == this.Top + 10
            && _amountText.Bottom == this.Bottom - 20

            && _dollarText.Right == _amountText.Left
            && _dollarText.Top == _amountText.Top
        );
    }

    public void Update(Product product)
    {
        _titleText.Text = product.Title;
        _amountText.Text = product.Amount.ToString("0.00");
    }

}



The result looks like this:


To help with long-term maintainability I've organized this into three high-level methods:

  • SetViewProperties() 
  • AddViews(); and
  • ConstrainLayout()


Also, I've tried to keep one line per view instantiation in AddViews().  For example, AddDollarText() is a method that is solely responsible for instantiating a single view.

This small method approach may appear at first glance to be more work than worth.  However, I would highly encourage this approach because I've found that if you aren't careful when creating a new 100% code-based view, over time it will become long and gnarly with methods spanning hundreds of lines.

Does this approach actually seem cleaner?  Consider the following questions:

  • If you wanted to extract the color of the amount label into a cross platform variable, where would you initialize that color?  
  • How would you handle buttons with event subscriptions?
  • What if you needed to display an OutOfStockProduct using a completely different style of dollar and amount labels (subtype the view maybe)?

I hope you'll agree the answers are generally more obvious, that the design has more flexibility, and that this sets future maintainers up for success.

Summary


Hopefully this helps with how to build list-based UI's on Android without the AXML.  You can see the full example on github in ViewProductsActivity.cs.

I hope you'll consider taking the technique for a spin and deciding for yourself if your next Android project would be better without the AXML.

Sunday, January 8, 2017

Introducing EasyLayout.Droid For Simpler Xamarin Android Layouts

If you've done much Xamarin iOS work you're probably run into Frank Krueger's awesome framework, EasyLayout, that makes manually coded auto layout's considerably easier to read and maintain.

If you've ever wanted the same type of functionality for Xamarin Android either for consistency or ease of cross platform code sharing, now you can with EasyLayout.Droid.


What Is EasyLayout?


The original EasyLayout takes Xamarin iOS code like this:

_passwordField.AddConstraint(NSLayoutConstraint.Create(
    _passwordField, NSLayoutAttribute.Top, NSLayoutRelation.Equal,
    _usernameTextField, NSLayoutAttribute.Bottom, 1f, 20f));
_passwordField.AddConstraint(NSLayoutConstraint.Create(
    _passwordField, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal,
    View, NSLayoutAttribute.CenterX, 1f, 0f));

And turns it into this:

View.ConstrainLayout(() =>
    _passwordField.Frame.Top == _usernameTextField.Frame.Bottom + 20 &&
    _passwordField.Frame.GetCenterX() == View.Frame.GetCenterX()
    );

If you're on a team, or storyboards just aren't your thing it's a lifesaver!


What's Wrong with Android .axml?


Android's axml files are ok, but on large projects they take a long time to generate, and they make it hard to share layout information cross platform.  But if you try to code Android by hand, you quickly discover the same type of verbosity that Xamarin iOS had.  Enter EasyLayout.Droid.

Example 1 - Parent Align


If you want to align an image to the edges of the frame you used to do this:

var layoutParams = new RelativeLayout.LayoutParams(
    ViewGroup.LayoutParams.MatchParent,
    ViewGroup.LayoutParams.MatchParent);
layoutParams.AddRule(LayoutRules.AlignParentTop);
layoutParams.AddRule(LayoutRules.AlignParentBottom);
layoutParams.AddRule(LayoutRules.AlignParentRight);
layoutParams.AddRule(LayoutRules.AlignParentLeft);
_image.LayoutParams = layoutParams;

Now you can do this:

relativeLayout.ConstrainLayout(() =>
    _image.Top == relativeLayout.Top
    && _image.Right == relativeLayout.Right
    && _image.Left == relativeLayout.Left
    && _image.Bottom == relativeLayout.Bottom
    );

There's no need to set LayoutParams at all.  If they don't exist EasyLayout.Droid will add them, if they do EasyLayout.Droid will append to them.  And if you don't add them it will take care of choosing LayoutParams.MatchParent or WrapContent.

Example 2 - Relative Alignment and Constants


If you wanted to align an image 20 dp under another image and center align it to the parent you used to do this:

var layoutParams = new RelativeLayout.LayoutParams(
    ViewGroup.LayoutParams.WrapContent,
    ViewGroup.LayoutParams.WrapContent);
layoutParams.AddRule(LayoutRules.CenterHorizontal);
layoutParams.AddRule(LayoutRules.AlignBottom, image1.Id);
layoutParams.TopMargin = DpToPx(20);
_image2.LayoutParams = layoutParams;


There's a couple of gotchas.  

  1. If you set the TopMargin to 20, then Android assumes you mean pixels not device independent pixels.  To fix that you need to remember to call a function like DpToPx().  
  2. Your relative view (image1) needs to have an Id.  If you forget to set it there's no error, it just does strange layout things.

EasyLayout.Droid replaces the code above with:

relativeLayout.ConstrainLayout(() =>
    _image2.Top == _image1.Bottom + 20
    && _image2.GetCenterX() == relativeLayout.GetCenterX()
    );


That's less code, and it's easier to read, plus there's some other small benefits: 

  1. If you forget to add an Id to _image1, EasyLayout.Droid will throw a helpful runtime error.  
  2. EasyLayout.Droid always assumes every number is in Dp, so it automatically converts all literals for you.

Incidentally, GetCenterX() is one of a couple of new extension methods along with GetCenterY() and GetCenter().

Example 3 - Constants


Constants weren't difficult to work with previously, but for completeness they used to work like this:

var layoutParams = new RelativeLayout.LayoutParams(
    DpToPx(50),
    DpToPx(ViewModel.SomeHeight);
_image2.LayoutParams = layoutParams;


With EasyLayout.Droid you can do this:

relativeLayout.ConstrainLayout(() =>
    _image.Width == 50
    && _image.Height == ViewModel.SomeHeight.ToConst()
    );


As mentioned previously 50 will be assumed to be in dp and will be auto-converted to pixels.  Also arbitrary properties such as SomeHeight will need the .ToConst() extension method applied in order to tell EasyLayout.Droid that they should be treated as constants.

Limitations


Android relative layouts are far from a replacement for iOS's auto layout.  To that end you cannot do the following operations that EasyLayout could:

  • Constraining Heights or Widths to be equal to the Heights or Widths of other elements
  • Using >= or <= signs to indicate GreaterThanOrEqual or LessThanOrEqual type constraints
  • Multiplication of elements (e.g. _image2.Width == _image1.Width * .25f)

Installation


If you want to add this to your project you can either install via NuGet (safer):

Install-Package EasyLayout.Droid

or if you think it's perfect as-is (you don't want updates) you can copy EasyLayoutDroid.cs into your source.  Next using EasyLayout.Droid and you're good to go.

Conclusion


Hope this helps make someone's Xamarin Android day a little better.  The code is MIT licensed.  If you have any questions please contact me on twitter.


Benefits Of Git Rebase

In the first article in this series (Git: Rebase vs Merge) I covered the tactics of rebasing.  I discussed what merge commits are, and how to avoid them with rebasing.  In this post I'll cover the benefits of rebasing, including how its use speeds up finding hard to track down bugs via git blame and git bisect.

Is Rebase Really Worth It?


I worked on one large project that discouraged rebasing a while ago.  In short, this was the result:


The repository was insanely complicated to look at with gitk (or SourceTree in this case).  Trying to understand what was going on across the program was virtually impossible.
The recalcitrant developer (hey, someone forced me to learn these stupid SAT words, might as well inflict them on others), at this point, might simply respond: "So what?  As long as my stuff works."  And perhaps it's true, just like the old joke:
Patient: Doctor, it hurts whenever I do this.
Doctor: Don't do that.

Merge Commits: Ugly, Painful, or Both?


But even if someone never looked at the repo with gitk, or a fancy git GUI, in the project above roughly 50% of all commits were merge commits!
That meant 50% of the history of the project was useless noise distributed like a shotgun blast into the true history.  Furthermore, some of those merge commits hid sneaky little bugs introduced when merge conflicts were poorly resolved.  
Granted, 50 percent may be high for most projects.  The actual number depends on how many developers, how often they commit, and how often they merge.  For example if developers pull from develop daily, a given project will get one merge commit per developer per day.  If developers commit 3 times per day (seems kind of average from my observations), then 25% of commits will be merge commits.
The recalcitrant developer (stupid SAT) might again at this point respond: "25% of commits are merges, so what?  Just don't look at the commit history!  And if forced to, just ignore the merge commits!"
However, there are two specific cases where a messy history may yet affect a fast and loose, merge happy team.

Don't Blame Git!


Have you ever looked at a file and wondered who wrote a particular line of crap?  In my case 99% of the time that person is me.  But never mind that.  The tool for this job is clearly git blame.
The command git blame (or git annotate for the more more politically correct) will annotate every line of a particular file with its last commit.  Apart from finding who wrote something, it's also an essential tool for discovering why something was done the way it was when spelunking through larger, or especially older, codebases.
However, merge commits obfuscate git blame.  If an associated commit message is simply "merged develop into feature.awesome" and the developer is no longer around to ask, then we have to go through additional effort to track down history.

For instance, in the example above line 3 "c" was actually created by commit C (b49c7b1), but git blame incorrectly shows the merge commit (d9400d4) as the author.

Git Bisect For The Win!

The second scenario in which merge commits complicate history is in tracking a bug that someone introduced, typically, within the last few days.  One could manually checkout every commit between the good commit and the bad commit, or simply use git bisect.  
Git bisect is wonderful for automating the process of finding a commit.  It allows you to specify the last known good commit, and the last known bad commit, and then it performs a binary search of all the commits in between to discover the bad commit as quickly as possible.
Regardless of whether you search manually, or use git bisect, life gets hard as soon as you try to juggle many branches with lots of merge commits.  The automated approach makes navigating many merged branches easier, but either way if you have a fully merge-based project, you are now required to take an additional 25-50% steps.   If each step takes time to build or deploy, these extra steps can quickly add up (trust me, I've had to do this a lot).
For instance consider the following project with three feature branches, seven real commits, and five merge commits.
gitbisect2.png
Now pretend that you've come in on Monday morning to discover that after committing D on Friday, some developers over the weekend committed E and F, and suddenly there's a hard to track down bug.  Git bisect will solve it for you like this: 
Lee@lee-xps MINGW64 /c/Temp/deletemegit (feature.awesomeness)
$ git bisect start
Lee@lee-xps MINGW64 /c/Temp/deletemegit (feature.awesomeness|BISECTING)
$ git bisect good 880e84a
Lee@lee-xps MINGW64 /c/Temp/deletemegit (feature.awesomeness|BISECTING)
$ git bisect bad be49c0d
Bisecting: 5 revisions left to test after this (roughly 3 steps)
[d9400d4c62807046f8ea235170e681b3e8952200] Merge branch 'develop' into feature.awesomeness
Lee@lee-xps MINGW64 /c/Temp/deletemegit ((d9400d4...)|BISECTING)
$ git bisect good
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[96cf2877d16183cccce1f822d72626d331b582ef] Merge branch 'develop' into feature.awesomeness
Lee@lee-xps MINGW64 /c/Temp/deletemegit ((96cf287...)|BISECTING)
$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 1 step)
[190dadb1046822fc169193e86484a19e3543b783] Merge branch 'feature.2' into develop
Lee@lee-xps MINGW64 /c/Temp/deletemegit ((190dadb...)|BISECTING)
$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[ef4a5532ace6075aead0850f088322f98e7afbf1] E
Lee@lee-xps MINGW64 /c/Temp/deletemegit ((ef4a553...)|BISECTING)
$ git bisect bad
ef4a5532ace6075aead0850f088322f98e7afbf1 is the first bad commit
commit ef4a5532ace6075aead0850f088322f98e7afbf1
Author: Lee Richardson
Date:   Mon Sep 5 21:56:07 2016 -0400
    E
:000000 100644 0000000000000000000000000000000000000000 d8263ee9860594d2806b0dfd1bfd17528b0ba2a4 A      2.txt
Lee@lee-xps MINGW64 /c/Temp/deletemegit ((ef4a553...)|BISECTING)
$ git bisect reset
Previous HEAD position was ef4a553... E
Switched to branch 'feature.awesomeness'
Your branch is based on 'origin/feature.awesomeness', but the upstream is gone.
  (use "git branch --unset-upstream" to fixup)
Lee@lee-xps MINGW64 /c/Temp/deletemegit (feature.awesomeness)

Beautiful!  Commit E was the culprit.  The only problem: three of the four steps wouldn't have been needed if the team had been rebasing.

Summary


If your project is still small, and you haven't had to use git blame or git bisect yet, you may not find these arguments compelling.  However, if this is the case I suspect you may also  not find much value in unit testing either.  Unit testing and rebasing both require extra up-front work in order to build quality into your work and set your future self up for success.
More worried about immediate deadlines than your future self?  Consider the developer who will replace you when you leave.  Not worried about her?  Consider your customer at some future point in time when they're attempting to spelunk through your code.  Unless this is a throwaway project, the chances are good that a little extra effort learning a new technique today could save yourself, other developers, and possible a future employer considerable time and energy.  Not a bad investment.