Sessions is temporarily moving to YouTube, check out all our new videos here.

Hybrid Apps with Ember/Glimmer

Alex Blom speaking at EmberFest in October, 2017
Great talks, fired to your inbox 👌
No junk, no spam, just great talks. Unsubscribe any time.

About this talk

For many, the first hybrid experience is one of poor performance and frustrating code management. Common techniques such as 'keep a shallow dom' and 'watch reflow' feel reasonable but hard to achieve, and even harder to contextualize in Ember. In this talk, we'll first cover the basics of hybrid apps before building a basic hybrid Ember app with ember-cordova, and will then focus on issues such as application structure, graceful mobile web fallbacks, animation optimization & handling native integrations such as push.


So I'm here to talk about building hybrid apps with Ember and Glimmer. Actually the library and stuff I'm going to show you works the exact same way with React, same with Angular. We're now working on an agnostic way to do that. Before I get into that though, just quickly, who am I? So, Alex, as you know. Historically I've been a back-end dev, probably like most of you. lot of Rails, a lot of Ghosts. Started with Java and things like that. And I really got into Ember about four years ago, 4 1/2 years a go, something like that. Just as I started to get more into hybrid apps, I realised that this was a good way to structure them and that was my appeal with Ember. So, what we're going to cover today is just, Why do we wanna do hybrid to start with? Probably every month there's somebody asking in Slack, Why would I do this? Cordova feels pretty gnarly. Why would I touch something like a Cordova? I'm gonna talk about wrapping your existing PWA and appyifying what we're doing. So, how do we add icons, splash, push notifications? The kinds of things that people would expect in a Native app, that you wouldn't have on your PWA. And, if we have time, we'll go into adding some custom plug-ins and just talking a bit about performance. So, before we get started, just to cover what Cordova is ... If you ever hear people talk about PhoneGap, imagine they're basically interchangeable. It was originally called PhoneGap. There was a point under which Adobe acquired the team who had built it. Cordova represents the open-source piece that was donated to Apache Foundation. It is the main engine. So you can think that PhoneGap actually implements Cordova and just has some proprietary stuff on top. Frankly, I just use Cordova all the time. You're completely safe with that. So, what Cordova does is, it goes and creates an app that's a full-screen web view. The way we do it is on iOS. That means you're using a WKWebView, if you're familiar with that, And on Android you're just using whatever system view comes with it. It adds a relatively stable window API as well so you can start interacting with actual Cordova things. And each platform you need to add manually. So, if you want iOS or Android, you go and plug in the platforms you wanna distribute to. Before everybody was on Electron, which we do prefer, you could also use Cordova to build desktop apps in the same way. So, if you're kind of curious how Cordova is structured, and this will matter a lot as we get deeper into the talk, you have this CLI which, when you're building with Ember, is completely optional. You don't need to use cordova-cli. Then there's this thing called cordova-lib. That's basically where all the guts are. That's how Cordova handles plug-ins. That's how you interact with, am I running on the right platform? Is my device ready, is the battery low? Stuff like that. Then we have our app. We have the different platforms that we've installed, and any plug-ins that you want added as well. If you wanna interact with the camera, if you wanna interact with the network, or something like that. Over the last year, especially with plug-ins, there's been a drive in the Cordova community to rewrite these plug-ins that are actually compatible with the way the web APIs work. If you're new to Cordova and you wonder why are these completely different than the way it works on web, you've just gotta keep in mind that Cordova is really, really, really old in some places. So, a lot of times this was built before it was built anywhere else and nobody's just sat down to catch it up. Recently, they actually switched and Cordova plug-ins work on NPM now. I remember just having to argue with people all the time who would ask, why didn't this work on NPM years ago. It's because, when they first built this, NPM wasn't really a thing. So they just went and implemented it themselves. So, before we get into some guts, why do we wanna do this? There's a few reasons. You may not actually have the resources to go and learn Swift, to go and learn Java for each platform. You might already have a set of Ember developers who wanna work this way. Depending on the type of app your building, Cordova is really good for your phone-heavy apps. Things like NetBanks and stuff like that. It is honestly less good if you're gonna try and build a mobile game or something like that. And also just developer sanity. Most importantly, if we're shipping this way, we're gonna keep feature-parity between iOS and Android. Which is something that, if you've ever worked in a company that's building Native, Native! You always have this problem where a feature's ready on iOS, it's gonna take a little bit longer on Android. Do we have different features for different sets of users, or how do we handle this problem? And most of the time we're actually holding back chips because the company wants everything to be released for everybody at once. Let's just sidestep a whole bunch of problems there. The way we build this with Ember, I'm gonna go through actually building an entire app through the talk, is with Corber. And this is actually a project that I maintain. It's an extension of Ember-Cordova, which was also something that we were working on, if you've ever heard of that. And Corber was, basically, us taking Ember-Cordova and making that work with Vue, making that work with React, making that work with Angular, making that work with Glimmer. The reason we did this is, as I said, Cordova is old, and we're starting to replace a lot of the guts. And as we do that, we need more help from the community and the broader we aim, the easier it is for us to start pulling people in to help us do that. So, it's basically a CLI to take an existing JavaScript framework, integrate into whatever build pipeline you already have, and let you start spitting out hybrid apps that way without really having to think about what you're doing. It also adds some extra features for you which I'll demo, such as Livereload. Actually works on a physical device so you can flash to an Android, a couple of different iOS devices, as you're developing. And, as you change code, you'll see that kind of reflect through. Add some unified builds, splash, stuff like that. At the moment, Corber is still quite young. It's out of beta, but our current goal is really to take an existing PWA you've already built and make that work as a hybrid app. We're not yet trying to focus on letting you build something that's just for mobile in the way you could with React Native. If you're only targeting mobile platforms this is great if you wanna use Ember, but something like React Native is also a good choice. It gives you a clean way to add native-only functionality, which I'll show you. So we've spent quite a bit of time thinking about how do I do a push-notification in my Ember app, but having a graceful fallback when I'm shipping to mobile web? Or something like that. And to take advantage of the best parts of Cordova, so that's where slowly replacing parts of that system, we're also realising that the main reason that everybody wants to stick with this is the wealth of plug-ins that are out there, no matter what you're trying to do. There's even a plug-in to do indoor lie-down mapping at this point. So there's a whole set of things you can just take off the shelf and plug into your existing app, that we wanna keep as we go. So before we get into it, just a couple of things because, sometimes people get a bit confused. We're not a UI library. So if you've heard of something like Ionic, we seem to get compared to that a lot. We're not trying to give you pre-build components at this point in time. I'd love to do it. We just don't necessarily have the time right now. We're not a component library. We have no opinion on which CSS framework you should use. There's a couple of ports of Framework7 floating around which is kind of where we're putting our attention. That's not what we're trying to deliver you yet. We're trying to deliver you something that can take what you've already done for mobile and ship it into a Native shell. Just quickly before we get into a demo, I actually said this at Ember Conf when I first introduced the Ember Cordova library. A lot of the times people think that these hybrid apps are slow, sluggy, it's not performing well. Most of the time that's just because somebody's written garbage JavaScript and doesn't really understand the performance. So, the problem is that when you're shipping into the shell, you don't really have the benefit of the doubt anymore. You're working on a less-powerful machine in a lot of cases. So you have to be really careful with what you're doing. Last point before we get into actually building together, what is the difference between React-Native here, because this comes up all the time. So, again, we're trying to recycle your existing web code and let you put that in a Native shell. We're not trying to get you to write something custom that's only going to work on mobile. React-Native, you can put in your web React code, but most of the time people are working and they're actually writing mobile-only components that don't even fall back to mobile web. So, it's a completely different target. We're focused on this constant recycling of code we've already written. For a large app, we'll still stick with doing this. If you're just building something that's a little marketware site, or whatever, React-Native is a perfectly reasonable choice. If you're only targeting two platforms, iOS and Android, it is something you can consider. Okay, I gotta go back to clicking. So, in terms of getting started, what we do is ... I'm doing this from an Ember project right now. Again, doesn't matter which framework you're doing. You just run in and type in corber init. Once you've installed the library, you can do that with the honour NPM and it's gonna go and do some stuff in your existing project which you will see here. I was doing this on a tether, so the yarn install's a bit slower than I'd like. But this is going through and, basically, setting up your project to be shipped a hybrid without breaking anything that's already there. What it does is it adds that Corber directory to your folder. It's top-level, so it's the same level as app basically. And inside of that we have all of our custom configuration for the hybrid build. And then the actual Cordova directory in the builds that have been put in there, as well. This clicker's going crazy. One thing to keep in mind here is that, when we actually initialise, we have very different defaults to the rest of Cordova. If you're not familiar, there are actually two different WebViews for iOS. There's the UIWebView which is the older one, and WKWebView which is newer. WKWebView is significantly faster, but it comes with some drawbacks. For example, if you're wanting to have images that are stored on the file system, you have to reference them a little bit differently. So we actually initialise you with a default VKWebView for performance reasons. If you're having problems with that you can also go and downgrade the WebViews you're working with. What we need to do next is decide which platforms we wanna take our existing app and ship them to. So what I'm going to do is just go through and basically say that I want to ship for iOS. You can have multiples. You can have as many as you want in here. This is going through and just installing the iOS platform to our Cordova directory on our behalf. You'll see here it's alerting us that we're going to WKWebView. You can see it's gone through and actually added iOS to that platform or to that folder on your behalf. The next thing we wanna do is, we wanna build our existing app. The app I'm working on here is just the standard Ember welcome page so we can all get a feel for what's going on. So I'm just gonna go through and run corber build. And what's going to happen is, it's actually going to throw an error and tell us what parts of our Ember application aren't going to work in this context. So if you never worked in this environment before, there are two things that every Emberist needs to change to ship into a Native shell. The first one is actually in your config environment. The base URL or root URL, depending on which Ember version you're running on, cannot have a leading, forward slash. The reason for this is all Cordova paths start in the file system. So, if you kick with a leading slash it's actually gonna try and pull a file from the machine. And you're gonna wonder why nothing is resolving as you're going through a route transition. So, typically, your best option is just to have that as a blank string or something like that. Likewise, your location type actually needs to be hashed for the same reason. Basically, we can never reference a path that starts with a forward slash, in this environment. If you've ever worked with Electron, or something like that, same concept, same reason. So what we'll go in is we just go into our config environment, update location type to hash. Then we'll go in next and base URL to a blank string. We're gonna run build again. So that's passed all of our validators now. The Ember build's finished. It actually builds to a different directory than disc, so it can be passed to Cordova. And now it's going through all of that Cordova compilation for iOS right now. One day I'm gonna build a better logger output here, so you don't get all of this default stuff, just time, higher-priority stuff like that. So now this has been built, there's another command you can run here called corber open. The default is iOS. Again you can always pass a platform tag and that's actually going to go through and just pop open the Xcode project on your behalf so you don't need to deal with that. And I'm just gonna go and flash it to an emulator. I'm doing this in Xcode for the demo. There's also a run command if you actually just wanna flash to a device from the CLI itself. And that's it. We're already running in an emulator. Our Ember app's been wrapped on our behalf. This is a straight-up iOS app that we can already ship to the store if we want to. So it's been pretty easy so far. As I said earlier, it doesn't matter what framework you're using. Those same commands, that same process, is going to work for you. So if you're using Glimmer, for example, it doesn't matter. Next thing we're gonna think about though is, you probably noticed that build wasn't as fast as we might like. It took 30 seconds, 45 seconds to do all that compilation. And, as you're developing, that gets really annoying, especially as you're dealing with some of the gnarlier edge cases. So we have this Serve implementation as well. So what you can do in is actually go and run corber s, and it's going to build a special version of the app. This would not pass app store rules because it's doing a lot of stuff that's technically against it. But it's okay if you're just developing locally. So this is going to create a special container that we're going to flash. Again, you can flash this container to as many devices as you want. And once it's up, you'll see that we can go and change code and it's automatically going to update in all the containers for us. Loading, loading ... So we have that up. We're just going to go through and say delete the welcome page and replace the text so you can see that working. And that's already updated for us. My typical dev flow is I'll have my iPhone Plus or have the smaller iPhone 4 and one Android device, that are all running this at the same time. So, as I'm coding, I'm basically watching those results flow through to each of the apps. Another important thing to note about this is your plug-ins will actually still work with the way we've implemented Serve. And I'm gonna show you some plug-ins in a second, but if you've gone and built, say a camera integration, a file system integration, something like that, it's going to continue working even though you're in this environment. So, you probably noticed though that there was that really ugly opening page, which was just the default Cordova logo. So, again, this is a hybrid app. We wanna go and put some icons and splash in there. So we have this other command that you can use which is basically make-icons and make-splash. What you do is, you give us a single SVG, the dimensions are listed on the web page for you, and we go through and actually re-size that to all of the different resolutions that youll need for iOS and Android, because you need to have 30, 40 variants to actually be allowed to ship on the app store. So this will actually go through and just re-size the icons that I put in there and make them work on each platform. So, make-icons and make-splashes. Also everything always runs slower when you're recording with QuickTime at the same time. So that's been done, what we're gonna do next is just go and show you that that worked and the amount of that stuff we just did for you. So, those are all the icons that you need to be allowed to ship. We've actually gone through, generated all those on your behalf and put them in the config as well. So, once you've done that, you do need to do a rebuild, because otherwise the icons won't show up in the actual Xcode container or the Android container for you. So we'll go through and rebuild and just see that those icons and splashes have showed up for us. Could have done this with library load by the way. Just, as you're doing QuickTime recordings for a talk, it tends to all be isolated as you're going. So that build's finished. And we're ust gonna go and open Xcode and check that that worked. You can already see in the top left our icon's changed to a red square. That's just because that's the icon I used. I just gave it a giant red image because I didn't really feel like making anything else. But I'm gonna go and just open the app as well and make sure that our splash worked and, great, we have a splash page that's not the default anymore. So, again, that's been pretty simple so far. It's a bit of a problem, just with that default implementation though, especially as you're working with Ember, the actual JavaScript init time can be a little bit longer than Cordova's init time. The way the splash works, by default, is it actually hides when the Cordova deviceready event fires, which is often much sooner than Ember is finished loading data. So, from an Ember perspective, what we wanna do is control when that splash is going to close, and only make it close once Ember has finished loading. What we wanna do is go and instal the splash plug-in, which gives us a more-granular control over when does this show, when does this hide. So any Cordova plug-in, you can instal just by corber-plugin-add, name of plugin. So, in this case we're going to instal the splash plug-in, which is one of the core parts of Cordova. Now what we wanna do is change two things inside of our Ember app. The first thing we need to do is change a Cordova preference and tell it to not hide the splash screen on the deviceready event. The second thing we're going to do is a best practise, which is, on our application route, after model hook, that's when we wanna hide the splash screen because that's when we know Ember's loaded and all of our data's come in. So we don't have that awkward hybrid app thing as well as one loading screen which then transitions to our Ember loading screen, which is kind of awkward. So what we'll do, we'll go into config. We're just going to up preference here. And it's basically called AutoHideSplash and we're going to set that to false. Should be reasonably obvious what that does. And then our application route, just once everything's finished loading, we're going to go and call NavigatorUpSplash.hide I have some examples a bit later about how to handle this gracefully if you're also running in a web end and NavigatorSplashScreen may not be defined. So, once we've set it up this way, we now have a guarantee that our splash is going to stay there until Ember's finished loading. If you're using Ember, we actually have built Ember-cordova/splash, which lets you write code like this instead. So when you're off the model hook, you can type this.gethide; and this is completely safe. It's built in a way that if you're running in mobile web, not a problem, it's not going to throw an error or anything like that. So if you're implementing other plug-ins as well, you can dig into the guts of these to see how it handled stuff like that. For Ember users, we've also built an event binding dev Cordova's events for you, Same type of graceful fallback. These are all Ember addons that just inject into your project. These are the events that we open up for you. So, deviceready, and deviceready is a special case in Cordova if you've never used it before, If you drop a deviceready listener and the device is already ready, it will fire immediately for you. They did this explicitly because a lot of JavaScript people were worried about dropping that hook, in case the hook was dropped after Cordova had finished initialising. You can see we can also see if the app session was paused or resumed, if a call came in while they were using the application, if we just lost internet, if our battery's running low, and we wanna start putting data somewhere else, You'll typically wanna bind a lot of these events in some service you've built around Cordova. So, just to give you an example of what that looks like, I've gone through here and just said, once the Cordova device is ready, I just wanna console logout that everything is good. We have a nice little binding to the environment we're dealing in here, that's not going to break in any other context. Likewise, there's another service you can use as an Emberist called Platform. And this is just something that gives you a bit of context on what environment are we running in now. So you can go and say, "Are we Cordova," for example, and my iOS and my Android and what version am I running here? For whatever information you might wanna collect, if it's just for analytics, if you wanna give some personalization, something you can do as well. All of these listed on corporate RAO as well, if you go and take a look and just read through the plug-in docs. Last one I'll show you here is Keyboard, which lets us go through again, a bit of a complex example here. But it lets us go through and actually hide and pop-up the keyboard when we wanna control it. Sometimes in Cordova you don't want the keyboard to pop up on every input. Or, you explicitly want to default open the keyboard before, say, somebody's clicking-in an input. So, this is what will let you do that. Next thing we'll talk about is just how do we structure these applications. 'Cause often we're recycling something that's not just a mobile PWA, but is also a desktop Ember app. And we're generally bundling these things together. So, the way we approach this is that we'll typically go and re-implement the layout, but not the component. And what I mean by that is, we don't try and do a bunch of media queries in terms of, great, how do I take this honkin' big desktop thing and scale it down to a mobile view. We'll handle that for single components, but when it comes to actual route templates or something like that, we'll go in and use Flexi instead, which lets us go in and, instead of having template.hbs I can have -mobile.hbs and -desktop.hbs in my pod and the right one will render in the right environment for you. So, again, for those larger, bigger routes, we'll go through and write something that's explicit for mobile here and trust that it will render in this context. A quick note on the plug-ins. Typically the reason you're gonna start thinking about using this is, you've also found a really good plug-in like, again, indoor lie-down mapping, for example, that you wanna use in this ecosystem. So, the way we always handle this is we'll take a Cordova plug-in we wanna use and wrap it as an Ember Service in every case. And again the reason we do this is it gives us a graceful fallback if we're shipping to mobile web or something like that. This how we've implemented every plug-in that we've exposed for other users. Just to quickly close off, because I only have a couple of minutes left. Deviceready we've already covered, but some other things that people run into all the time. You always wanna go through and put this little CSS rule in your app's CSS if you're shipping to this context. The reason for this is mobile Safari works a little bit differently than you might expect in this context and it raises touch versus clicks on anything versus a link. So, sometimes, say, you might have actions bound to a div, or you doing something like that, and you're sitting there wondering why nothing's firing and nothing's working. The way you get around that is just, for anything that's an Ember action, set the cursor to a pointer. And Safari's going to raise the right event on your behalf now. Everything's going to continue working out-of-the-box for you. Ghost clicks, as well are something you wanna be aware of. So what you're gonna go through in this environment is, you're probably going to go and use something like ... Hammerjs is what we would use to go and bind a touch up, touch down, swipe, scroll events like that. And then often what will happen is you'll forget to go and disable the click events on the same node. And you're gonna have these weird problems where it will click, start transitioning and another click event will fire and Ember's going to start doing everything again for you. So, just be very conscious that when you're dropping your touch events, touch move, whatever, that you go and disable clicks in the same context. Typically what I'll do is I just go and globally disable click everywhere. I'll go and put touch events on my Ember actions. Then I'll just custom implement whatever touches and swipes I want. The reason I do that is Hammerjs is also going to work on mobile web for you. So it's something that's gonna be compatible in every app. One of the last things I'll point out here is on the web, there's this rule that when you click a link, it's been highlighted, we know we visited before. It's not something that mobile apps do. So, what you wanna do is just go through and make sure that once a link's been clicked that we're not going to go and highlight that colour. So, go and put a CSS rule here that says, don't do anything in this context. And, again, these are just little things we do, so a user doesn't think we're running in a hybrid context. So, in the last couple of minutes, I just demoed a really quick way that you can go into your existing app, build that new application cell and push it out. We're always working on this and we're trying to make it better. So, up until today, it's really been a focus of, just how can we make that process pretty simple for people who've never touched that before. But I also understand, Cordova is old, it's sometimes hard to understand. Sometimes the end can feel a bit gnarly. So the stuff we're working on now is, basically, making it easier to flash to a device without using Xcode and to have run command and things like that. Also improving, if you've ever been through, say, provisioning a new iOS device or setting up your push certificates or something like that. That's really challenging if you've not done it before and you don't know what Apple's asking you for. We've also been working on some code that automates and handles all that on your behalf. And the other thing we're working on lately is just going through and gutting all the parts of Cordova that are causing problems for people and starting to re-implement them ourselves. So, basically, what that means, is on that earlier slide, there was cordova-lib and platform iOS and platform Android. So actually working on morphing to a point where we just consume cordova-iOS, cordova-Android but that lib we've actually re-implemented ourselves. What that will let us do is just put a whole lot more barriers around your dev experience for you so you're not getting trolled by this environment every five minutes or 15 minutes if you've not set it up before. So, thank you very much. I think I have a couple minutes left if there are any questions. Otherwise, thank you.