HomeGuidesReferenceLearn
ArchiveExpo SnackDiscord and ForumsNewsletter

Expo Modules API: Overview

An overview of the APIs and utilities provided by Expo to develop native modules.


What are Expo Modules

Sometimes, installing an Expo package or a third-party library is not enough when looking to extend the capabilities of your app. In that scenario, you can write custom native code to tap into native platform APIs or use available Android or iOS dependencies. Luckily, Expo has first class support for this in the form of Expo Modules.

Expo Modules allow you to write native code in a way that feels natural with minimal boilerplate that is also consistent on both platforms. It provides a set of APIs and utilities to improve the process of developing native modules for Expo and React Native and expand your app capabilities.

Native Modules

Create native modules using Swift and Kotlin.

Android Lifecycle Listeners

Hook into Android Activity and Application lifecycle events.

iOS AppDelegate Subscribers

Respond to iOS AppDelegate events.

expo-module.config.json

Configure and opt in to features.

Design considerations

Our team needs to maintain a large set of libraries, and maintaining native modules over time and in a constantly changing environment is quite expensive. We found out that the existing React Native API for native modules is not scaling very well and is giving us some additional overhead on many levels, from onboarding new contributors (lack of good documentation) to writing a lot of boilerplate code. To make the maintenance easier and let the community iterate faster, we decided to create a module system that reduces the costs and technical debt to a minimum.

Why Swift and Kotlin?

After several years of maintaining over 50 native modules in the Expo SDK, we have discovered that many issues were caused by unhandled null values or incorrect types. Modern language features can help developers avoid these bugs; for example, the lack of optional types combined with the dynamism of Objective-C made it tough to catch certain classes of bugs that would have been caught by the compiler in Swift.

Another difficulty of writing React Native modules is context switching between the vastly different languages and paradigms for writing native modules on each platform. Due to the differences between these platforms, it cannot be avoided completely. We feel the need to have just one common API and documentation to simplify the development as much as possible and make it easier for a single developer to maintain a library on multiple platforms.

This is one of the reasons why the Expo Modules ecosystem was designed from the ground up to be used with modern native languages: Swift and Kotlin.

Passing data between runtimes

Another big pain point we have encountered is the validation of arguments passed from JavaScript to native functions. This is especially error-prone, time-consuming, and difficult to maintain when it comes to NSDictionary or ReadableMap, where the type of values is unknown in runtime, and each property needs to be validated separately by the developer. A valuable feature of the Expo native modules API is its full knowledge of the argument types the native function expects. It can pre-validate and convert the arguments for you! The dictionaries can be represented as native structs that we call Records. Knowing the argument types, it is also possible to automatically convert arguments to some platform-specific types (for example, { x: number, y: number } or [number, number] can be translated to CoreGraphics's CGPoint for your convenience).

React Native New Architecture

React Native version 0.68 introduced the New Architecture, which offers developers new capabilities for building mobile apps. It consists of the new native modules system called Turbo Modules and the new rendering system called Fabric. Native libraries need to be adapted to take advantage of these new systems. For Fabric, it needs even more work as it doesn't provide any compatibility layer, which means that view managers written in the old way don't work with Fabric and the other way around — Fabric native components don't work with the old renderer. It basically implies that existing libraries have to provide support for both architectures for a while, increasing the technical debt.

The new architecture is mostly written in C++, so you may end up writing some C++ code for your library as well. As we all, React Native developers, use high-level JavaScript on a daily basis, we are rather reluctant to write C++, which is on the opposite side of the spectrum. Moreover, including C++ code in the library has a negative impact on build times, especially on Android, and can be more difficult to debug.

We took these into account when designing the Expo Modules API with the goal in mind to make it renderer-agnostic, so that the module doesn't need to know whether the app is run on the new architecture or not, significantly reducing the cost for library developers.