"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
— Sarah Mei (@sarahmei) January 12, 2017
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)
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.