If It Weren’t For Apple, Hybrid App Development Would Be The Clear Winner Over Native

24,785 reads

Over the last 6 months, I’ve been building Rizer, a mobile app that allows users to judge photo battles from multiple categories (currently Animals, Babies, Food, Funny, Men, Nature, and Women) as well as submit their own photos to these categories to be judged. Photos are ranked using the ELO algorithm (what Eduardo Saverin drew on the window during that awesome FaceMash scene in The Social Network), with the highest rated being displayed on Rizer’s Leaderboard.

Rizer is a Hybrid app that uses Apache Cordova and its plethora of native plugins to gain access to important native functionality on the user’s device such as: receiving push notifications, taking pictures with the camera, and processing in-app purchases.

I currently dominate the Men’s Leaderboard 😎

From the outset, it was critically important to me that the average user had no idea that Rizer was a Hybrid app. This meant it needed to be well-designed, performant (hey that’s not a word!), not lacking any native features a user would expect from this kind of app, and not devolve into uselessness the moment the device lost its network connection. Thankfully, according to my 40 family and friend beta testers, I achieved this goal (and I might even agree with them).

If you don’t believe me, or if it has been awhile since you played around with what you knew was definitely a Hybrid app, then I encourage you to download Rizer, judge some battles, check out a few categories on the Leaderboard, and if you are feeling brave, submit some of your own photos. You just might walk away impressed with what a Hybrid app is capable of, and if not, at least you got to see some cute animals and babies.

Make No Mistake, Going Hybrid Is The Right Choice For The Vast Majority Of Apps

It is important to understand three key points here:

A Hybrid version of an app will never be as fast as a native version of the same app, but it doesn’t matter as long as the Hybrid version is fast enough. Put simply, if your users never complain about the performance of your app then it is fast enough. As a performance junkie, I understand the appeal of knowing that my app is running as fast as it possibly can, but we must be careful to avoid premature and over optimization. It might make me feel all warm and fuzzy inside knowing that my Native app renders 100 photos on the page 8ms faster than the Hybrid version, but do my users even notice or care? No.

Put simply, if your users never complain about the performance of your app then it is fast enough. As a performance junkie, I understand the appeal of knowing that my app is running as fast as it possibly can, but we must be careful to avoid premature and over optimization. It might make me feel all warm and fuzzy inside knowing that my Native app renders 100 photos on the page 8ms faster than the Hybrid version, but do my users even notice or care? No. A resource-hungry 3D game or similar does not fit into my definition of “app” . In these cases a native app is almost certainly your best option because a Hybrid app will struggle to be fast enough. Canvas performance has greatly improved in recent years, but older devices will still most likely run a resource-hungry app too slowly.

. In these cases a native app is almost certainly your best option because a Hybrid app will struggle to be fast enough. Canvas performance has greatly improved in recent years, but older devices will still most likely run a resource-hungry app too slowly. Well-designed Hybrid apps running on top of a modern JavaScript UI framework are definitely fast enough. Bad performance can no longer be used as an excuse to ditch the Hybrid approach. If you have recently tried making a Hybrid app and have not been satisfied with the performance then you need to seriously reevaluate your chosen design patterns and/or use of massive libraries like jQuery that you don’t need. Learn the modern DOM API if you haven’t. It is supported on all of the devices our apps target.

There are some major advantages to the Hybrid approach:

You get to utilize your existing JS, HTML, and CSS skillset to develop a single codebase that runs on both iOS and Android. This is the obvious key advantage to Hybrid apps but it really can’t be overstated. There are less than 10 times throughout my project that I needed to branch the code based on the platform the app was running on (this was only ever necessary to fix an iOS bug, but more on that later). A branch in the code would look something like this:

if (device.platform === "iOS") {

doRidiculousWorkaround();

} else {

doThingThatWorksAsExpected();

}

Viewing changes on your device during development is nearly instantaneous. Throughout development, I tested all of my code changes on my Galaxy S7 Edge. The phone was plugged into my PC with USB debugging enabled, running a “debug” version of Rizer. If you type chrome://inspect/#devices into Chrome’s URL bar, you will see a list of all Chrome tabs running on any device connected to your computer. By clicking on Rizer in the list, a new DevTools window opens up and I am able to fully analyze the app like I would a normal website. Pairing this capability with a live reload tool in my build system, I am able to make a change to my code and see the updated result on my device’s screen within seconds. Nice.

Throughout development, I tested all of my code changes on my Galaxy S7 Edge. The phone was plugged into my PC with USB debugging enabled, running a “debug” version of Rizer. If you type into Chrome’s URL bar, you will see a list of all Chrome tabs running on any device connected to your computer. By clicking on Rizer in the list, a new DevTools window opens up and I am able to fully analyze the app like I would a normal website. Pairing this capability with a live reload tool in my build system, I am able to make a change to my code and see the updated result on my device’s screen within seconds. Nice. You can push updated JS, CSS, HTML, fonts, and images directly to all of your users without needing to recompile the app’s binary and getting App Store approval . Rizer uses Microsoft’s CodePush service (which is currently free) and its Cordova plugin to check for an update every time it launches, and I honestly don’t know what I would do without it. Alternatively, Ionic’s Deploy can be used in the same way, but since it’s not free and I wasn’t using Ionic, it didn’t make sense to use.

. Rizer uses Microsoft’s CodePush service (which is currently free) and its Cordova plugin to check for an update every time it launches, and I honestly don’t know what I would do without it. Alternatively, Ionic’s Deploy can be used in the same way, but since it’s not free and I wasn’t using Ionic, it didn’t make sense to use. You can pick the UI framework and build tools that you are most comfortable with. Rizer runs on top of my own frontend framework Samson.js (Do not bother using this, it’s undocumented and constantly evolving to fit my personal needs), but I could have successfully built it on top of React, Vue, or Ionic/Angular.

“Wow Sam, those advantages sound great! Are there any disadvantages?”

There is ONE major disadvantage to the Hybrid approach:

We as developers still need to care about iOS, which means we have to put up with Apple’s BS. Hey that rhymed.

You might be confused. Even if we went with the Native approach, we would still care about iOS and therefore would still need to put up with Apple’s BS, right?

Kinda.

Here’s some Apple BS we can’t avoid no matter what approach we use:

Xcode

Provisioning Profiles

Needing a Mac to compile the app

Needing to use TestFlight for beta builds

The app review process. Admittedly, the app review time has dropped to about 2 days on average, but when your app gets rejected 7 times like Rizer did (for legitimately silly reasons), your patience can really start wearing thin.

In addition to the above, Hybrid apps also have to deal with this BS:

The iOS WebView

Wait, what? Can the iOS WebView really be that bad? Yes.

A Tale Of Two WebViews

The JavaScript code included in our Hybrid app runs in a WebView, which is essentially a native browser bundled with app. On Android, our code runs in a modern Chromium-based WebView that for the most part works exactly as it should. On iOS, we have the choice of running our code in one of two WebViews: UIWebView or WKWebView.

Choosing between these two WebViews sucks.

UIWebView was introduced in iOS 2 and was the only choice until WKWebView was added in iOS 8. UIWebView has great compatibility with most of the existing Cordova plugins, but is overall bloated and slow. There is little difference in HTML 5 support between these two WebViews, but there is a dramatic performance difference due to WKWebView’s use of the Nitro JavaScript engine.

According to Nitishkumar Singh, WKWebView provides:

A massive reduction in RAM usage, faster startup, JIT JavaScript compilation, out-of-process rendering, reduced memory usage, improved stability, better security, up-to-date web standards, etc.

Before the launch of iOS 8 in 2014, many articles touting what a big deal WKWebView was for Hybrid apps started popping up. There was a lot of excitement amongst Hybrid app developers to be able to take advantage of the above optimizations that WKWebView provides.

Unfortunately, when iOS 8 finally launched, WKWebView couldn’t just be dropped into existing Hybrid apps due to its lack of some requisite functionality. A full 3 years later, the situation has only slightly improved.

The Current State Of WKWebView

Thanks to some fantastic work from the Ionic team, some masochistic developers (myself included) are utilizing WKWebView in their Hybrid apps. I will be the first to say that when WKWebvView works, it really works. Rizer cold-launches and installs CodePush updates dramatically faster on my wife’s iPhone 7 than on my Galaxy S7 Edge. But what about when it doesn’t work? Let’s dig in.

WKWebView issues that require hacky workarounds or complete abandonment of functionality:

Inability to access files that don’t exist in the app’s root “www” folder.

Numerous CORS (Cross-Origin Resource Sharing) issues, including being unable to extract image data from a canvas that was drawn using a local image. This was nearly a showstopper for Rizer because I needed to allow users to crop and add filters to their photos, both of which require manipulating the image in a canvas and then extracting the pixel data from it. I say nearly a showstopper, because I was actually able to workaround this by branching my code on iOS and doing the following ridiculousness:

1. Pass the fileURL of the photo we just took on the device to the FileReader API's "readAsArrayBuffer" function.

2. Convert the arrayBuffer returned from the "readAsArrayBuffer" function into a base64 string using the following function:

function arrayBufferToBase64(arrayBuffer) {

var binary = '';

var bytes = new Uint8Array(arrayBuffer);

var len = bytes.byteLength;

for (var i = 0; i < len; i++) {

binary += String.fromCharCode(bytes[i]);

}

return window.btoa(binary);

}

3. Finish creating our DOM-readable "imageDataURL" by prepending "data:image/jpeg;base64," to our returned base64 string

4. Create a new Image, load our imageDataURL as its "src", and once the image is done loading, create a new canvas element and draw the image into it. It looks something like this:

var image = new Image();

image.onload = function() {

var canvas = document.createElement("canvas");

canvas.width = image.width;

canvas.height = image.height;

canvas.getContext("2d").drawImage(image, 0, 0);

callback(canvas); // No CORS issues

};

image.src = imageDataURL;

5. Throw your computer out the window :)

Unpredictable vertical scrolling behavior, and in my experience, completely broken horizontal scrolling . There were times my wife would load Rizer’s Leaderboard page and be unable to scroll through the photos without going back to the previous page and then reloading the Leaderboard page. This was such a problem that I ended up needing to implement iScroll. Ugh.

. There were times my wife would load Rizer’s Leaderboard page and be unable to scroll through the photos without going back to the previous page and then reloading the Leaderboard page. This was such a problem that I ended up needing to implement iScroll. Ugh. Complete lack of getUserMedia support . This wasn’t a showstopper for Rizer but it has prevented me from starting another project where it would be. This is completely unacceptable in 2017, and provides further evidence that Safari is the new Internet Explorer.

. This wasn’t a showstopper for Rizer but it has prevented me from starting another project where it would be. This is completely unacceptable in 2017, and provides further evidence that Safari is the new Internet Explorer. No programmatic control over showing and hiding the keyboard

App-breaking conflicts with stable versions of the Cordova SplashScreen plugin

App-breaking conflicts with stable versions of the Cordova In-App Browser plugin

It is imperative that you understand that none of the above issues exist for Hybrid apps running on Android.

In all seriousness, I have virtually no complaints about the app development-to-publishing process that Google has built. The Google Play Developer Console is incredible, with robust release management, A/B testing, in-app product creation, order management, app install and usage analytics, user feedback collection, and lack of an absurd review process. And the icing on the cake is that we know our Hybrid app will render and function exactly as we expect thanks to Android’s Chromium-based WebView. Apple is so far behind here that I can’t even talk about Google’s offering without sounding like a fanboy.

Without a doubt, creating Rizer was a longer and more frustrating experience than I expected it be… thanks to Apple.

My family can attest to how often I complained about Apple and iOS when they asked me how Rizer’s development was progressing. I am continually blown away by the fact that the most valuable company in the world has created such an abysmal development experience.

I have tried to figure out why Apple isn’t motivated to streamline their Hybrid development process and bring Safari up to feature parity with the other modern browsers (it’s not like they don’t have the resources to do it), or simply allow the use of 3rd-party browsers like every other platform (including Windows) does. Are they trying to get developers to build their native iOS app before its Android equivalent, potentially making it less appealing for an iPhone/iPad owner to switch to an Android device since the apps they use everyday aren’t in Google Play? I really hope not.

OK, So… Hybrid Or Native?

In my experience, all of the potential show-stopping issues caused by going with the Hybrid approach have workarounds. They are frustrating to discover and implement, but they do work. For this reason, I am comfortable stating that the Hybrid approach is the right choice for your next app. It will absolutely take you longer to develop a Hybrid app than it should, but it will still take less time than developing two separate Native apps, and if properly designed, will feel just as fast as to your users.

And hey, maybe one day in the future, Apple will course-correct and provide us with the development experience we deserve. Until that day, I fully expect this “Hybrid vs Native” debate to rage on.

Tags