Limitless App Development with Expo and React Native


App development is hard, React and Expo make it easy!

It's never been simpler to build and deploy powerful mobile apps with incredible features to both Android and iOS users all over the world.

We’ll discuss building and deploying mobile apps seamlessly from the cloud using EAS, creating powerful dev clients (like browsers but for mobile app development) for testing your app, pushing OTA updates instantly to users, and much more — no native experience required!


What's up everyone? I'm Evan Bacon and today we're going to be talking about building cross-platform apps with react and Expo. Now native app development can be a little bit daunting so I'm going to be discussing and describing in a way which is comparable to web development. So first we're going to do a quick recap for anyone who's unfamiliar with react Native. When you want to build an iOS app you'll use Swift and Objective-C and when you want to build an Android app you'll use Java and Kotlin. Now this often means that you'll have two separate teams which are pretty much working in silos. Obviously that's less efficient than having one team but using react Native we can write react and javascript that renders to each platform using the same code base. This is very similar to a mobile browser which lets you run react DOM code on multiple different platforms which is by design. react is very intuitive for building and scaling large front-end applications. Now the core differences between react DOM and react Native are instead of drawing our views to Skia like we would in Chrome, react Native uses proper native views. This means that we can use platform optimizations like smooth scrolling, gestures, and complex animations which is known to be a weak point in mobile browsers. We can create custom native clients that expose extra native functionality to our app. Think of this like building a browser but with Bluetooth support added to it. And we can reuse the majority of our application code across multiple different platforms. We have the potential to scale our app to support more platforms in the future. And finally we can utilize multiple different threads. By default everything runs in a javascript thread minimizing the amount of business logic that runs on the main UI thread. This is a pretty advanced architecture which you just get for free by simply using react Native so it's pretty awesome. Now react Native of course is not without its issues ranging from mildly annoying to highly annoying. So let's talk about some of them. First is that in order to get a Hello World app running, you know just Hello World, we need to create a native runtime. This differs from web development where the native runtime is already built ahead of time and downloaded on your computer. That would be Chrome or Safari, the web browsers, and they can just load your javascript project inside of them. Second is that dependencies in react Native are much harder to manage than in web development. In react Native you need to install the native code via npm whereas in web development the native code is already built ahead of time into the browser. And the the APIs and native code are designed by W3C so they're very well like thought out and just well constructed. Third is that your app's runtime must be distributed through the App Store or Google Play Store with a few exceptions for testing of course. This means that you must perform code signing for your application which is complicated and prone to errors. Finally, upgrading your react Native application is quite painful. You need some familiarity with both iOS and Android native development in order to do it and upgrades happen pretty frequently. Over the course of this presentation I'm going to show you how we've solved each one of these issues using a series of tools called Expo. Now Expo provides many tools that web developers have come to expect like a reusable pre-built runtime similar to the browser or a thoughtful set of features and native APIs that you don't need to really interact with too much, very fast iteration speed, and deeply integrated cloud services that make building and deploying very seamless. First we'll talk about creating an app and running it on your devices without having to do any native builds. Then we'll cover the development cycle and adding dependencies. Then we'll talk about publishing your app to the store so that you can get it into the hands of users. And finally we'll talk about adding native code, customizing your client, and easily upgrading your app. To get started with app development we just need to install Expo CLI. This is a modern node.js CLI that helps us interface with the bundler, start up a dev server, and many other development tasks. So we're just going to globally install the package and then create a new project with Expo init. Here we'll quickly generate a new project. We don't need any native code to get started. When the project is initialized we can use npm scripts in order to start up instantly on iOS, Android, web, which are the platforms that are supported by Expo by default. yarn Android starts up the metro bundler and opens up the project on Android. yarn iOS opens up the simulator. And web starts up the webpack dev server and opens your project in the browser. Now Expo in react Native's development cycle is pretty top-notch, even just like for web development. So here I'll open the app on both iOS and Android just instantly. I don't need to do any native builds for that. And then I'll make a few changes to demonstrate fast refresh which preserves the react's component state between updates. And as you can see the development cycle is pretty fast and really enjoyable. So unlike react DOM development, react Native uses primitives. So instead of div you'll use something like view, instead of image you'll use image, and instead of like span you'll use text. Each one of these elements then draws to the correct native view for each platform. So on iOS a view would render to a UI view and on web a view would render to a div and so on and so forth. And finally these primitives render out to something which generally looks the same across all platforms. react Native only provides a handful of core primitives. The majority of complex features come from community packages. Now a really good set of community packages is the Expo SDK which is a set of npm packages which are versioned together and always work together. And this is probably the closest thing that react Native has to a w3c type spec at the moment. Each package is typed and supports iOS, Android, web. The reliable versioning system and complete platform support in Expo SDK effectively solves the dependency issue that users frequently encounter in react Native. All the native code is then built ahead of time into an app called Expo Go. The reason we've been able to instantly open our app on iOS and Android without having to make any native build is because we're using a reusable native runtime called Expo Go. You can download Expo Go from the iOS app store and Google Play store and it has most of the APIs that you'll need to make complex native apps built in ahead of time. And this drastically reduces the complexity and the amount of time that it takes to get from nothing to hello world, effectively making it about as easy as web development. When we run Expo Start, the terminal shows our bundler dev server URL in QR form. We can scan this with our smartphone camera to skip over having to manually enter in the URL. Now you may notice that we use exp colon slash slash instead of http colon slash slash. This indicates to the device that it should use Expo Go to open the link instead of the web browser. So let's talk about what's going on behind the scenes. When the app opens up the URL, it'll ping the server for the app manifest. Now think of this like the index.html but for react Native apps where html isn't supported. The server will return the manifest and the client will parse it. So the client will parse the manifest down to the bundle URL field which indicates where the JS bundle is being hosted. Think of this like an html script tag telling the browser where to download external assets like the javascript bundle from. The client will request the URL and the dev server will return results from the metro bundler. After that, the app starts right up. And as you can see, the whole cycle is very akin to web development and how a web browser interacts with a local dev server. And it is akin to web development because this is only for development mode. Because of app store restrictions, you cannot use this to distribute production apps to your users. All distribution must go through the Apple App Store and Google Play Store. So let's do that right now. Traditionally, deploying mobile apps is very difficult and also very error-prone. But with a new tool called Expo Application Services or EAS for short, we can submit our app with a single command. As a side note, EAS is still in beta and it should be out of beta in early November 2021. So by the time you're watching this, about a week. EAS build works by automatically bootstrapping your app's native code signing and performing other validation locally, then sending the source code up to the EAS build servers. This is useful because native builds require a lot of local resources which are hard to validate and just get set up correctly. And we've also found that a lot of developers just don't have macOS machines which is required for building on iOS apps. So this opens up the door for performing iOS development on any device. And after the build is complete, the CI servers can return the binary files or it can automatically push them to the stores. So all you need to do is configure a bit of metadata and submit for review. All we need to do is install EAS CLI globally with npm or yarn and then run a single command which is EAS build dash dash auto submit. The auto submit flag is actually brand new by the way and this reduces it from two steps, EAS build and EAS submit, just to one step which is very exciting. It's about as easy as web development is now. The CLI will prompt you to sign into your Apple developer account, then proceed to automatically configure your app for the store based on the state of your project. When it's done, your code will be uploaded. Now a relatively new step which I'm actually talking about for the first time here is Apple capability signing. Now capabilities are APIs provided by Apple. They're used for things like payments, notifications, health kit, etc. And if they aren't configured properly with Apple servers then your app will fail to build, which is a very weird time to fail. Now this type of build failure is to be very common and super annoying. So for enabling payments for instance, you need to create a merchant ID, you need to enable payments functionality in your bundle ID. This is all through Apple's developer website by the way. You have to do this all manually through a UI. Then you need to register that bundle ID, register the merchant ID to the bundle ID, and then finally you need to regenerate all your profiles and re-sign your app. A bunch of just weird terms that require a lot of hand-holding to get right. But since react and Expo are deeply integrated with EAS build, you just need to define the merchant ID and then rebuild your app. The entire process that I just specified is completely automated. So there's just this entire classification of build errors which are just completely eliminated from your development cycle, which is very exciting. When the build submission is complete, you can download the binary file if you want and you can go check out the app entry in the store. Apple will process the binary for a while after it's complete, then you just need to fill out some metadata and you can submit the app for review. You can also download it using test flight and again all this happened from just one command. So pretty exciting stuff. Now one of the most groundbreaking features in react and Expo is over-the-air updates. So if you want to update your JS and assets in your app, you can do that instantly via Expo CLI. By running Expo Publish, your JS will be bundled and pushed to a hosting service which your runtime is coded to check for. When you push your update to the server, it waits for the user to open the app again. When they do that, the update is downloaded in the background while the initial bundle is immediately presented to the user. Then the next time they open the app, the cached version v2 will be shown. This is very similar to the stale while validating caching policy in a web service worker. The reason why we use this specific caching policy is because it's optimized for offline first and for very quick TTI, which are both things that you come to expect when you use a native app. Expo's hosted service is capable of pushing JS bundles and static assets over the air to your app instantly, which is very similar to web hosting services. However, it's not capable of pushing the native code for your project. Due to app store restrictions, all of the iOS app must be pushed through the iOS app store and all the Android code must be pushed through the Google Play Store, which EAS Build can automate for you. So there is essentially a part of the stack for everything. So now that we've talked about deployment, let's talk a little bit about native code. Up until this point, we've only covered the react code and haven't had to interact with any custom native modules. But eventually you may find that you need some custom code or third party library that isn't available in Expo Go or the Expo SDK. Now for this, we can easily create a custom development client, which is like Expo Go, but with a special set of native features. The development client sits between the OS and the react app. So kind of think of it like a web browser, but special and native. Now to actually add custom native code, very tricky, all you have to do is npm install any react native module into your project and then rebuild the app. So still no native code is directly in sight for you as the user. Native code is linked to our project using three distinct tools. The first is auto-linking, which links the native iOS and Android code that's shipped in the node module to your local iOS and Android code. And this happens during the native dependency manager installation. So it'd be CocoaPods or Gradle installs. Next is config plugins, which are simple javascript functions that are used to configure static values like permission messages. So if you install like a camera library, you might want to set what the camera permission message says. And these are all generated and evaluated during the Expo pre-building step. Finally, we have Expo modules, which we'll get into a little bit later, but it's an api that's used to interact with app-wide mechanisms like detecting how your app was opened, if it was opened through like a link or a notification press, and this is all executed during runtime. Now all of these mechanisms are automatically run and used when you build your app with Expo CLI or EAS build to create the native code for your javascript project. We can rebuild our native client locally with Expo CLI or remotely with EAS CLI. So we've already discussed EAS building a little bit, so let's talk about local builds. Now to build your app locally, you do need native build tools installed on your computer. This means Xcode for iOS and Android Studio for Android. These tools are both very complex and have little in common, so using them can be pretty jarring and different. So since we only need to utilize the building functionality of both of these tools, we've created commands to streamline interfacing with them. That's the exporunios command and the exporunandroid command. Now these tools are designed to make iOS and Android builds very familiar for node.js developers. For instance, Xcode isn't aware of node modules, it just sees more project files. So exporunios is able to contextualize the information and only show you logs and warnings that come from application code and things that you can actually make meaningful changes to, which effectively means that your basic build has zero warnings instead of like 538, which is what Xcode would normally show you. Now these commands also do a bunch of other things to make your life really easy, but we don't have time to get into everything, so I really quickly want to cover building development clients in the cloud. There is like one pretty core issue that needs to be covered, and that is that react Native apps have this hard-coded URL. Now that hard-coded URL is basically defined by whatever network your computer is on when you build the app. So if you're doing this in the cloud, that IP is going to not match up with the IP that you're using locally. So it'll make a request to the dev server, and then that URL won't match what the dev server is hosted on. Also there could be other factors like maybe the port changes, or maybe you've got like a VPN. Many things could happen, and then the dev client won't connect. So the dev server will effectively return 404 and your app won't be able to load. Now to fix this, we created a library called ExpoDevClient, which you can install in your project. Now custom clients by default are very close to what you'll actually ship to the app store in the sense that they don't have all the great DX and UI tools which you can use to like make your development experience easier. The main one that we're describing here and talking about is the URL bar not being present. So you can't really switch between different apps, and you can't connect to different networks. To combat this, we created ExpoDevClient, which is a package that you can install to get all of these UI tools back in your application. So your dev client is very similar to ExpoGo in development mode. With this, we can pick the URL that actually works, we can make a request to our dev server, and the dev server can return the manifest, which effectively loads our app as expected. And of course all this extra code is then stripped out of your app when you build it for production. So now lastly, I want to cover workflows in Expo. Expo has two main workflows, the managed workflow and the bare workflow. In short, managed workflow lets us mostly avoid having to work with native code directly. It also maximizes cross-platform configuration, whereas bare workflow lets us have full control over everything in our project. The main trade-off being that if you want to do something like change the name of your app or update the icon, you have to do it for every native project individually. To explain this better, let's look at a bare project. A bare workflow project is pretty much just like a react Native project without Expo, in the sense that you have these two native projects which have lots of complexity in them, and you're responsible for everything inside of them. And this is great because you just have full control over every aspect of it, and you can make it work however you want. But one major drawback to this is when you want to upgrade your app. So react Native frequently has upgrades. I think that I'm hoping that these versions are still correct by the time you're watching this, where if you're trying to go from react Native 65 to react Native 66, the line between them is very long. First, you need to validate the versions of your packages. Every package that you're using, you have to make sure that it works with the latest version of react Native. Then you need to upgrade all of your packages, and then you need to upgrade all the native configuration. So if you've made any custom native config and things like your info.plist, your Android manifest, you have to manually go in, make sure that that works with the latest version of react Native. Then, and this is the very difficult part, you need to mix in native changes from the new version of react Native with your current version. So when you generate your react Native project, you generally, it's just kind of like you set it and forget it. But when you upgrade, you need to be pretty familiar with all of the inner workings of your template files. And finally, you rebuild the app and hope everything worked as expected. And this scenario has effectively created one of the hardest problems in react Native, which is upgrading frequently, it's very difficult to do. Now, in an Expo-managed project, your iOS and your Android folders are managed by Expo CLI, and they're configured using the app.json config file. So I like to explain this using npm, because people are very familiar with npm. npm has a CLI, and it has a config file, the package.json, and then it has a generated folder, node modules, and because of the system, you can very easily add like lots of complex modules and just remove them and you can delete it, you don't have to git commit any of that, and it really drastically reduces the complexity of your project as it scales upward. And then whenever you want to change something, you can just run npm install, and it will delete the node modules and effectively regenerate them and apply new changes. Now, Expo CLI works very similarly, where you have an app.json config file, which configures Expo CLI, so you can run a command called expo-prebuild to effectively delete and then regenerate your iOS and your Android projects based off of the state of your app.json, your package.json, and things like that. Now, this effectively makes upgrading your react Native app as easy as upgrading just any react project. You just change some JS code and regenerate everything. And of course, if you ever need to write custom native code, you can just jump into the iOS and Android folder, similar to jumping into the node modules, and customize everything locally. The drawback of this is that then you aren't able to safely reuse the expo-prebuild command in the same way that you can't safely run npm install after manually modifying node modules. So there's that trade-off. Now, we've chosen to keep this as a first-class option, whereas modifying node modules isn't really a good practice at all, because the iOS and Android folder just have so much inner complexity, and we don't want anyone to feel locked in. So if there's ever anything that doesn't work, you kind of have this peace of mind that you can always just jump into the native code, take the wheel essentially, and configure things however you want. Now, coming back to our react Native issues, as you can see, Expo provides solutions for each one of these. Instead of having to install Xcode to make Hello World show up on your phone, you can just install Expo Go. Instead of having to figure out if packages work together in the same version, Expo SDK is very reliable in that sense, and when you want to deploy your app, you can do it in just one command. It's pretty sweet. And when you want to upgrade your code, you can do it just as easily as any node.js project. So pretty awesome. And now, with whatever time I have left, I want to show you guys some awesome new things that we have coming to Expo in the near future. Just like a node.js project, if you want to add custom packages, you're going to want to set up a monorepo or upgrade your project to a monorepo, which can link local Node modules to one or more applications. And I'm very proud to announce that with Expo SDK 43, which was just released in October, so earlier this month, has initial support for monorepos. This means that you can essentially write native code in a Node module and directly link it into your managed workflow project. So very, very limitless and very seamless native development right there. This adds first-class support for yarn workspaces, a way to keep you essentially in managed workflow while writing native code for your project, and also just allows you to link up many different projects, however you want. So I'm super excited that we now have this in react Native via Expo. Speaking of writing modules, I'm extremely excited about Expo modules. Now, this is a public version of the system that we use internally to write the Expo SDK. It drastically simplifies writing native code for your react app and provides many features to make your modules more self-contained and powerful. To explain this, let's look at how modern react Native works. If you want to write a simple function to just add two numbers together, you have to use complex languages like Objective-C and Java, which then install features onto the JS global using something called JSI. That makes them effectively available to react. This is also made extremely difficult by the lack of clear documentation on how to even write a JSI module. So Expo modules core essentially sits between JSI and your code to provide a frankly beautiful DSL where you can write Swift and Kotlin as first-class languages. There's going to be documentation as well, and those will bind directly to the javascript object via JSI. As you can see here on the left, we have a current react Native module written in Objective-C, and over here on the right, we have one written in Swift. As you may notice, if you're familiar with the internals of react Native, this isn't like some hacky solution in Swift where we have macros that bind to headers and Objective-C, and then you got to write some Objective-C in order to write some Swift. This is all first-class Swift support. It has a very modern DSL, and it just makes working on the native side of react Native super, super intuitive and nice. Now, even though this is pretty sweet, there is a pretty nice part of it, and that is that the architecture that we use in Expo modules is very fast. We use it for our SDK so that local iOS modules compile super fast. As you can see here, there's Expo Camera compared to the react Native community 4 of Expo Camera. We compile ahead of time versus everyone compiling locally when they get the module. So let's say, right here, it's 35 seconds. That's a lot. It doesn't even need to add up. I'm not just picking on that module. Here's Expo Updates against the community version. Here's Expo Location, and here's Expo File System. These are very, very fast. This saves a lot of time, a lot of money, especially if you're using CI servers. I am super excited that we are moving towards making this public so that people in the community can now have a much more solid and well-documented foundation for building very fast modules and getting up to that Expo SDK level of module quality. Tomasz, who created the api, wrote a really nice blog post explaining and going more into depth about this. So if you're interested, I highly recommend checking that out. That is all I have for you guys today. Thanks so much for watching. If you have any questions or comments, feel free to reach out to me on Twitter at BaconBricks or Expo. We also have a forum and Discord for community questions. Again, thanks so much, and I'll see you all online. Evan, thanks for joining us. Good to see you. Hey, what's up? Hey. Yeah, I can hear you. Good, good. Before we start the Q&A, I wanted to say that I've seen your Legos, and they are awesome. I don't know how you have time for this after a full-time job, but they looked awesome. And everyone that doesn't know what I'm talking about that's watching, look up Evan Bacon Lego, and you will be amazed. So we have some questions from the audience, and the first question is from Alice. Is Bear Expo the same thing as an ejected app? Yeah, so in the last two SDKs, we've kind of gotten rid of having to eject. It's not a thing that you need to do anymore. Whereas before, there was only the managed workflow, and then the Bear was kind of this side version where you would eject out of it, like create react app. But now we've restructured it where managed sits on top of Bear, and then you can kind of peel off layers to get as close to the metal as you want to get. So you can also add layers back on. It's just a lot more fluid now and a lot more flexible. Awesome. Thanks. And one more question. Will Expo modules be available for the Bear workflow apps? Yeah, Expo modules will be available everywhere for any kind of react native app, and they're really exciting. We actually released them yesterday, or we released the kind of first beta version. It's very exciting. All right, great. Well, Evan, thanks a lot for joining us and sharing the knowledge, and hope to see you again soon. Yeah, sweet. Thanks for having me. All right, bye-bye.
27 min
22 Oct, 2021

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic