1. Introduction to React Native
Today we're talking about building cross-platform apps with React and Expo. React Native allows us to write code once and render to multiple platforms. It uses proper native views for improved performance and allows us to reuse code across platforms. However, there are some challenges, such as setting up the native runtime and managing dependencies. The app's runtime must also be distributed to app stores.
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.
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.
2. Introduction to Expo and React Native Development
In this part, we'll discuss the challenges of code signing and upgrading React Native applications. We'll also introduce Expo, a set of tools that solve these issues. Expo provides a pre-built runtime, native APIs, fast iteration speed, and integrated cloud services. We'll cover creating an app, the development cycle, publishing to the store, adding native code, and upgrading the app. To get started, install Expo CLI, create a new project, and use NPM scripts to start on iOS, Android, or web. React Native's development cycle is fast and enjoyable, and it uses primitives to render native views for each platform. Community packages provide additional features.
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 web pack dev server, and opens your project in the browser.
Now, Expo in React Natives development cycle is pretty top-notch, even just like for web development. So here, I'll open the app on both iOS and Android, instantly. I don't need to do any native builds for that. Then I'll make a few changes to demonstrate fast refresh, which preserves the React's component state between updates. As you can see, the development cycle is pretty fast and really enjoyable. 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 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.
3. Expo SDK, Expo Go, EAS Build, and App Distribution
The Expo SDK is a set of npm packages that work together and solve dependency issues in React Native. Expo Go is a reusable native runtime that allows instant app opening without native builds. Expo Start provides a QR code for easy access to the app. The app manifest and JS bundle are fetched from the server, similar to web development. Distribution of production apps must go through the Apple App Store and Google Play Store. EAS build simplifies app deployment by handling native code signing and validation.
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, and web, the reliable versioning system, and complete platform support. The current 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. 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 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 We can submit our app with a single command. As a side note, EAS is still in beta and 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.
4. Building and Submitting iOS Apps with EAS Build
This section covers the process of building and submitting iOS apps using EAS build. It simplifies the configuration and submission steps, reducing them to a single command. The integration with React and Expo eliminates common build failures. Once the build is complete, you can download the binary file, submit the app for review, or use Test Flight. Another groundbreaking feature is over-the-air updates, allowing instant updates to JS and assets in the app.
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, 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, et cetera, 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 used 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 like 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 like 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.
5. App Updates and Native Code
When users update the app, the new version is downloaded in the background, while the initial bundle is immediately presented. The cached version v2 is shown on subsequent app openings. This caching policy is optimized for Offline First and quick TTI. Exynos Hosted Service can push JS bundles and static assets, but not native code. App Store restrictions require iOS apps to go through the App Store and Android code through Google Play Store, which EAS build automates.
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 revalidating 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.
Exynos 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 part of the stack for everything.
6. Adding Custom Native Code and Building Locally
To add custom native code, you can npm install any React Native module into your project and rebuild the app. Native code is linked using auto-linking, config plugins, and Expo modules. These mechanisms are used when building the app with Expo CLI or EAS build. To build locally, you need Xcode for iOS and Android Studio for Android. Expo CLI provides commands like exporun.ios and exporun.android to streamline the process.
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 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 exporun.ios 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.
7. Building Development Clients and Expo Workflows
React native apps have a hard-coded URL that may not match the dev server in the cloud. To solve this, Expo created ExpoDevClient, a package that brings back UI tools for the dev client. Expo has two workflows: Managed Workflow and Bare Workflow. Managed Workflow avoids native code and maximizes cross-platform configuration. Bare Workflow provides full control but requires individual changes for each native project. Upgrading apps in Bare Workflow can be challenging due to the long line between React Native versions.
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 of 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 Expo Go 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.
8. Upgrading React Native and Expo Managed Projects
In an Expo managed project, your iOS and Android folders are managed by Expo CLI and configured using the app.json file. Upgrading your React Native app is as easy as upgrading any React project. You can change JS code and regenerate everything. Custom Native code can be written in the iOS and Android folders. Xbort provides solutions for React Native issues, making it easier to deploy and upgrade your app.
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 that 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 it 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 like 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, Xbort 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 Xbort Go. Instead of having to figure out if packages work together in the same version, Xbort SDK is very reliable in that sense and when you want to deploy your app, you could do it just with 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.
9. Xbort SDK 43 and Expo Modules
Xbort SDK 43 now has initial support for monorepos, allowing you to write native code in a Node module and directly link it into your managed workflow project. Xbo modules simplify writing native code for React apps and provide first-class support for Swift and Kotlin. The architecture used in Expo Modules is very fast, resulting in significantly faster compilation times compared to community versions.
And now, with whatever time I have left, I want to show you guys some awesome new things that we have coming to Xbort 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 Xbort 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 Xbo.
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 it's I mean, right here it's 35 seconds. That's a lot of, it doesn't even need to add up. And I'm not just picking on that module. There's, here's Expo Updates against the community version. Here's Expo Location and here's Expo File System. So like, these are very, very fast. This saves a lot of time, a lot of money, especially if you're using CI servers.
10. Public Release and Q&A
We are making this public to provide a solid foundation for building fast modules and reaching Expo SDK level of quality. Tamash wrote a blog post with more details. Reach out to me on Twitter or Expo for questions or comments. We also have a forum and Discord. Thanks for joining us, Evan! Before the Q&A, I wanted to mention Evan's amazing Legos. Now, let's answer some audience questions. Bare Expo is no longer the same as an ejected app. Managed sits on top of Bare, allowing flexibility. Expo modules are available for all React Native apps. Thanks for sharing, Evan!
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 like Expo SDK level of like module quality. Tamash 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.
And 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 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 and 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 Bare Expo the same thing as an ejected app? Yes, 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 Bare was kind of this like kind of side like version where you would eject out of it like create React app, but now we've like restructured it where managed sits on top of Bare 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 and it's just like a lot more fluid now and a lot more flexible. Awesome, thanks. And one more question. Will Expo modules be available for the Bare 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 like beta version. It's very exciting. All right, great. Well Evan, thanks a lot for joining us and sharing the knowledge. And I hope to see you again soon. Yeah, sweet. Thanks for having me.