Let me start by saying I was a proponent of html/web-based mobile apps before it was cool. I wanted to do it back when it wasn’t even a good idea — the tools were too clunky. I told my (mobile) manager we should just do web-based mobile apps, and he facetiously asked why I was questioning every decision he ever made.
Now that I’m more seasoned, I still think going hybrid with your mobile apps is a good idea, but I think most people get talked into doing it wrong, and/or doing it for the wrong reasons. The mistake people make is that it’s not about the buzzwords. It’s not about switching to Cordova or Flutter or React Native or Xamarin or any particular framework.
The right way to share code across your different clients is to break them down into small pieces to reduce cascading breakages. You need to use a componentized architecture and use versioned packages to import code so that clients don’t all have to be updated in tandem.
I once worked at an enterprise product company where they had a very large suite of software which was based on on-premise deployment (cloud-hosted options were also available, making this a ‘hybrid’ offering.) Over 25 years, the product codebase became ridiculously large. New versions of the software were released approximately yearly, after millions of dollars of development and testing time. New releases generally only included a small number of headline items, bundled with some other maintenance work and marginal improvements.
Because of all the shared code and shared infrastructure, maintaining the large product could be a nightmare. Simple changes could take as long to push through as complicated new features. That’s because new features could be separated out as a microservice or separate repo, to avoid the mistakes of the past. Upgrades to old features would cause a cascading avalanche of cross-testing that needed to happen. A large QA team was needed to test small changes across a bevy of modules, some of which nobody really knew and no developer was even responsible for anymore.
This same trade-off is what happens every single time you make “code sharing” the main priority. If you try to create your mobile apps by wrapping your web app, simple changes to the website can now break the mobile app, and vice versa.
Can automated testing solve this? Not exactly. If you write automated tests for all your clients, and run them in CI, you’ll know when an invalid code change was made. This will do a lot to help prevent the resulting bugs (there will be many) from reaching production. But in exchange you will still be losing lots of work hours to making changes that pass all the tests. Automated tests help your customers, they don’t really do anything to help your engineers. You will also lose time to writing the tests. Keep in mind, for a web/mobile hybrid app, we’re talking about UI tests, which are generally the most costly and onerous to maintain.
When assessing the latest technology, you must try to figure out who the technology is actually for and how it compares to your current position.
The trendy technologies tend to favor start-up culture. They favor tools which allow you create fast proof-of-concept work. Iterate fast, fail fast. Naturally, these technologies tend to lead to higher maintenance costs in the long run. They are a form of debt financing. The idea is if the product fails, you won’t have to pay the long-term maintenance costs anyway.
Take, for example, Apache Cordova. Cordova lets you take your website and package it inside a mobile app. This technology lets you make a website and then go to your boss or customers or investors and say “we have a mobile app.” But, for the reasons described earlier in the article, this will slow you down in the long run. You’re trying to share ALL the code at once, so now you need to start cramming changes for different platforms into the same codebase. You need all manner of dynamic switches, toggles, tables, etc. Cordova is the shortest path toward the worst kind of shared-code hell many companies find themselves in.
What’s amusing, though, is you may have clicked into this article because you’re thinking about “switching to Cordova.” “Switching,” as in, you already have a mobile app, but you want to make it a Cordova app instead because you think that might decrease your costs. At risk of sounding like a broken record, I’m telling you the opposite will happen. Your costs will go up and your iteration speed will go down compared to maintaining separate apps, just like my old company which had hundreds of engineers take a year to ship marginal upgrades. Trying to use the same source code twice reduces short term costs, but it adds on to long term costs. That is, unless you do it the right way.
I already spoiled it in the first section — using a component architecture for your shared front-end code is far more important than choosing any particular magic tool.
Component architecture isn’t really a new idea. Old desktop and web forms frameworks always had a concept of “Controls” which would be reusable chunks of UI. But those were rarely actually cross-platform tools, so they never had much reason to exist other than to perhaps give your company a fancy date picker it could use on two different web pages.
Component architectures are for the frontend what microservices are for the backend. It’s a productivity enhancing drug for eliminating overlapping dependencies and allowing you to deploy different parts of your software asynchronously while still ultimately sharing infrastructure.
React and Angular (new) both follow highly component-ized design principals. The component itself follows object-oriented principals, with distinguished formal mechanisms for inputs and outputs. Angular even has built-in dependency injection for these purposes.
The trick is to build out those components, and then don’t share the source code between your different platforms. Instead, publish versioned components to a private package manager repository, then integrate them piece-by-piece into each client. When a client needs a new version, update the required component versions, test, and ship. Each client can be treated independently this way.
If you take this route, you can do hybrid apps without any particular framework. You’re free to upgrade piece-by-piece, and even mix-and-match web-based UI with native UI per screen. You can just do this fairly manually with webviews (that’s ultimately what Cordova uses, but you can do it piece-by-piece rather than all-or-nothing.)
The beauty of this approach is when you start to make changes to a component, you have no obligation to immediately update the version of that component on all your different client platforms. Different clients can be allowed to lag behind on older versions until they are ready to upgrade. You can also start to get into different models like forking code repositories when different client platforms can’t be made to gel with the same exact code.