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.
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.
if (device.platform === "iOS") {doRidiculousWorkaround();} else {doThingThatWorksAsExpected();}
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.“Wow Sam, those advantages sound great! Are there any disadvantages?”
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.
Wait, what? Can the iOS WebView really be that bad? Yes.
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.
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.
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 :)
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.
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.
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.