Building a React Native App Without Expo: A Comprehensive Guide
Expo is a favorite tool for creating mobile applications for many React Native app developers. It streamlines the process by providing pre-configured tools and services that enable you to immediately launch your app.
Sometimes, though, you might want to forego Expo and create your React Native app from scratch.
This article will assist you through the process whether you need greater control over native modules, want to maximize performance, or just want to stay away from some of Expo’s constraints.
Why Build a React Native App Without Expo?
While Expo provides a streamlined environment for React Native development, it also abstracts a lot of the underlying configurations. This can be both a blessing and a curse. By not using Expo, you gain several advantages:
Full Control Over Native Modules
Expo simplifies many tasks, but it also limits your ability to access and modify native code directly. For instance, if you want to integrate custom native modules or third-party libraries that require native code modifications, using Expo can be restrictive. Building without Expo allows you to have full control over native modules, enabling you to add features that may not be supported by Expo.
Better Performance
For large-scale applications, the overhead introduced by Expo can sometimes lead to performance bottlenecks. Expo apps tend to include a lot of pre-configured features that, while useful, may not be necessary for your specific app. By bypassing Expo, you can strip down your app to only include the features you need, resulting in faster load times and more efficient resource management.
No Dependency on Expo SDK Versions
Expo regularly updates its SDK, and while this ensures that you have access to the latest features, it can also be a limitation. If you need to use a version of React Native that is not yet supported by the latest Expo SDK, you’re stuck waiting for Expo to catch up. By building your app without Expo, you have the freedom to update React Native and other dependencies as soon as new versions are released.
Also read: React Native app targeting mobile, web, desktop with expo tauri.
Setting Up the Development Environment
Before you start building your React Native app without Expo, it’s essential to have the right development environment set up. Here’s a detailed guide to getting your environment ready:
Step 1: Install Node.js and npm/yarn
React Native relies on Node.js, a JavaScript runtime built on Chrome’s V8 JavaScript engine. npm (Node Package Manager) or yarn will help you manage your project’s dependencies. To install Node.js, visit the official Node.js website and download the latest stable version.
Once Node.js is installed, npm will be installed automatically. If you prefer using yarn, you can install it globally using npm:
npm install -g yarn
Step 2: Install React Native CLI
The React Native CLI is a command-line tool that enables you to initialize and manage React Native projects without relying on Expo. You can install it globally using npm or yarn:
npm install -g react-native-cli
This CLI will be your primary tool for creating new projects, running your app, and managing other development tasks.
Step 3: Install Java Development Kit (JDK)
If you’re developing for Android, you’ll need to install the Java Development Kit (JDK). The JDK provides the necessary tools for compiling and running Java code, which is essential for Android development.
You can download the JDK from the Oracle website or use an open-source alternative like OpenJDK.
After installation, ensure that the JDK is properly set up by adding it to your system’s PATH environment variable. This allows your system to recognize Java commands from the terminal.
Step 4: Set Up Android Studio
Android Studio is the official Integrated Development Environment (IDE) for Android development. It includes the Android SDK, which provides the necessary tools to build, test, and debug Android apps. Download and install Android Studio from the official website.
After installation, open Android Studio and follow the setup wizard to install the necessary components, including the Android SDK, Android Virtual Device (AVD), and any additional tools required for your development needs.
You will also need to set up the Android SDK location in your system environment variables:
- Windows: Go to System Properties > Environment Variables, and add a new variable named ANDROID_HOME pointing to the Android SDK directory.
- macOS/Linux: Add the following line to your .bash_profile, .zshrc, or equivalent:
1 2 3 4 5 6 7 8 9 |
export ANDROID_HOME=$HOME/Library/Android/sdk export PATH=$PATH:$ANDROID_HOME/emulator export PATH=$PATH:$ANDROID_HOME/tools export PATH=$PATH:$ANDROID_HOME/tools/bin export PATH=$PATH:$ANDROID_HOME/platform-tools |
Step 5: Install Xcode (For iOS Development)
If you’re targeting iOS, Xcode is an essential tool. Xcode is the official IDE for iOS app development services and includes the necessary tools to build, test, and debug iOS apps. You can download Xcode from the Mac App Store.
After installing Xcode, open it and agree to the terms and conditions. You may also need to install additional components, such as command-line tools, which Xcode will prompt you to install when you first run it.
Ensure that you have the latest version of Xcode, as older versions may not support the latest iOS features or may cause compatibility issues with React Native.
Initializing the React Native Project
With your environment set up, you’re ready to build and deploy a React Native project. Unlike Expo, where everything is handled behind the scenes, creating a project without Expo gives you full control over the process.
Step 1: Create a New Project
To create a new React Native project, use the React Native CLI:
react-native init MyApp
Replace MyApp with your desired project name. The react-native init command generates a new React Native project with all the necessary files and configurations. This includes native directories (android/ and ios/), JavaScript files, and the basic structure needed to start building your app.
Step 2: Understanding the Directory Structure
Once the project is initialized, you’ll notice a different directory structure compared to an Expo project. Here’s an overview of the key directories and files:
- android/: This directory contains Android-specific code and configurations. You’ll find Gradle build files, AndroidManifest.xml, and Java files for your Android app.
- ios/: This directory contains iOS-specific code and configurations. It includes Xcode project files, Info.plist, and Swift or Objective-C files for your iOS app.
- src/: Although this directory is not created by default, it’s a good practice to create a src/ directory for your application’s main code. This helps keep your project organized and separates your app logic from platform-specific code.
- App.js: This is the main entry point for your React Native application. You can start building your UI and logic here.
- node_modules/: This directory contains all the dependencies your project needs, installed via npm or yarn.
Managing Dependencies
One of the significant differences when building a React Native app without Expo is that you’ll need to manage dependencies manually. This offers greater flexibility but requires a deeper understanding of how React Native and its libraries work.
Step 1: Installing Libraries
Without Expo, you can install any library directly using npm or yarn. For example, if you want to add a library for navigation, you can install it like this:
npm install @react-navigation/native
After installing the navigation library, you may need to install additional dependencies and set up your project to use them:
npm install react-native-screens react-native-safe-area-context
For libraries that involve native code, additional steps may be required. React Native CLI automates much of this through auto-linking, but in some cases, you might need to manually link the library:
react-native link some-library
Step 2: Customizing the Build Process
When using Expo, the build process is handled for you. However, without Expo, you’ll need to manage your build configurations, especially if you want to customize the build process for Android or iOS.
For Android, you can modify the build.gradle files to customize how your app is built. This might involve setting different build types (e.g., debug, release), configuring ProGuard for code obfuscation, or setting up multi-APK support.
For iOS, you can modify the Xcode project settings. This might involve setting up different schemes for development and production, configuring the app’s signing and provisioning profiles, or optimizing the build settings for performance.
Running the App
With your dependencies installed and the build process configured, you’re ready to run your app. The process varies slightly depending on whether you’re targeting Android or iOS.
Step 1: Running on Android
To run your app on an Android device or emulator, use the following command:
react-native run-android
Before running this command, make sure you have an Android emulator running or a physical Android device connected to your computer via USB. The React Native CLI will handle the build process and automatically launch your app on the device.
Also read: How to create an MVP Android app in React Native framework
Step 2: Running on iOS
To run your app on an iOS device or simulator, use the following command:
react-native run-ios
This command will open the iOS Simulator and launch your app. If you want to run the app on a specific device or simulator, you can specify the device name:
react-native run-ios –device “iPhone 12”
For deployment to a physical iOS device, ensure that your Xcode project is configured with the correct signing and provisioning profiles. You may also need to trust the developer certificate on your iOS device.
Adding Native Modules
One of the main reasons to avoid Expo is the ability to add custom native modules. This is particularly useful when you need to access device features that Expo does not support or when you want to optimize performance by writing custom native code.
Step 1: Understanding Native Modules
Native modules are pieces of code written in platform-specific languages (Java/Kotlin for Android, Swift/Objective-C for iOS) that allow you to access device features or integrate with other native libraries. React Native provides a bridge that allows JavaScript code to communicate with these native modules.
For example, if you want to access a device’s camera or GPS, you might need to write or integrate a native module.
Step 2: Adding a Native Module
To add a native module, you’ll need to modify the code in the android/ or ios/ directories. Here’s a high-level overview of the steps:
Android:
- Navigate to the android/ directory and open the appropriate Java file in the app/src/main/java/com/myapp/ directory.
- Create a new Java class for your native module and extend it from ReactContextBaseJavaModule.
- Implement the methods you need and register the module with React Native.
iOS:
- Navigate to the ios/ directory and open the appropriate Swift or Objective-C file.
- Create a new class for your native module, ensuring it conforms to the RCTBridgeModule protocol.
- Implement the methods you need and register the module with React Native.
Step 3: Linking the Native Module
After creating the native module, you’ll need to link it with your React Native project. This step involves updating the MainApplication.java (for Android) or AppDelegate.m (for iOS) files to include the new module.
For Android, add the following to your MainApplication.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new YourNativeModulePackage() // Add your module here ); } |
For iOS, add the following to your AppDelegate.m:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#import <YourNativeModule.h> // Import your module - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"MyApp" initialProperties:nil]; // Additional setup code return YES; } |
Once linked, your React Native app development services can communicate with the native module, allowing you to use native device features directly from JavaScript.
Handling Updates and Dependencies
Without Expo managing your environment, keeping your app up-to-date is your responsibility. Regularly updating your dependencies is crucial to ensure your app remains secure, performant, and compatible with the latest versions of React Native and its libraries.
Step 1: Updating Dependencies
To check for updates to your dependencies, you can use the npm-check-updates tool. This tool compares your current dependencies with the latest versions available and updates your package.json accordingly.
First, install npm-check-updates globally:
npm install -g npm-check-updates
Then, check for updates:
ncu -u
This command updates your package.json with the latest versions of your dependencies. To install the updated dependencies, run:
npm install
Step 2: Updating React Native
Updating React Native itself can be more involved, as it may require you to update both JavaScript and native code. The React Native community provides a React Native Upgrade Helper tool that guides you through the upgrade process.
To update React Native, follow these steps:
- Update the react-native version in your package.json to the desired version.
- Run npm install or yarn install to install the updated version.
- Use the React Native Upgrade Helper to manually update any necessary files in the android/ and ios/ directories.
Regularly updating React Native and your dependencies ensures that your app benefits from the latest features, security patches, and performance improvements.
Debugging and Testing
Debugging and testing are essential parts of the development process. Without Expo, you’ll need to rely on different tools and techniques to ensure your app is bug-free and performs well across different devices and platforms.
Step 1: Debugging Tools
React Native provides several tools for debugging:
- React Developer Tools: A standalone app that allows you to inspect the React component hierarchy, view props and state, and debug the app’s UI.
- Chrome Developer Tools: You can use the Chrome DevTools to debug your JavaScript code. To do this, run your app in development mode and press Cmd+D (iOS) or Cmd+M (Android) to open the developer menu. Then, select “Debug” to open Chrome DevTools.
- Flipper: A desktop app for debugging mobile apps. It provides a wide range of tools, including network inspection, layout inspection, and performance monitoring.
Step 2: Unit Testing
Unit testing ensures that individual components of your app work as expected. You can use testing libraries like Jest, which comes pre-configured with React Native projects.
To write a unit test, create a test file in the __tests__/ directory and write your test cases using Jest syntax. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import React from 'react'; import { render } from '@testing-library/react-native'; import MyComponent from '../src/MyComponent'; test('renders correctly', () => { const { getByText } = render(<MyComponent />); expect(getByText('Hello, World!')).toBeTruthy(); }); |
Run the tests using the following command:
npm test
Step 3: Integration Testing
Integration testing ensures that different parts of your app work together correctly. You can use libraries like Detox for end-to-end testing of React Native apps.
Detox tests run on real devices or emulators, simulating user interactions to ensure your app functions correctly in a real-world scenario.
To set up Detox, follow the official documentation for installation and configuration. Once set up, you can write and run your integration tests.
Typical Mistakes and How to Prevent Them
You have greater control when building a React Native project without Expo, but there are drawbacks as well. The following are typical dangers and tips for avoiding them:
First Pitfall: Dependency Issues
You may get into issues with conflicts between various libraries or versions while manually handling dependencies. Runtime issues or build failures may result from this.
Solution: Make sure your dependencies are compatible with one another and periodically check for changes. To properly handle updates, use tools such as the React Native Upgrade Helper and npm-check-updates.
Pitfall 2: Problems with Native Code
It might be difficult to work with native code, particularly if you’re not experienced with iOS or Android programming. Incorrect setups, missing files, and compatibility issues can all cause issues.
Solution: Begin by learning the fundamentals of iOS and Android coding. To become proficient with native code, consult official documentation and community resources. If problems arise, look for answers in the React Native GitHub source and community forums.
Third Pitfall: Bottlenecks in Performance
Without Expo, you’ll have to manually improve the speed of your app. This may be difficult, particularly for intricate programs with many of dependencies and components.
Solution: Track the performance of your app and locate bottlenecks with tools like Flipper. Reduce the number of pointless re-renders, make advantage of memoization, and utilize as few heavy libraries as possible to optimize your code.
The Best Ways to Develop React Native Applications Without Expense
Use these recommended practices to guarantee a seamless app development process and high quality product:
Best Practice 1: Organize Your Codebase.
Keeping the codebase well-organized is essential to long-term maintainability. For the primary code of your project, use a consistent file structure, such the src/ directory. To make your project easier to explore, group together utilities, services, and similar components.
Best Practice 2: Compose Modular Code
It is simpler to test, debug, and maintain modular programs. Segment your application into manageable, reusable parts that each take care of a certain UI element or piece of logic. This facilitates updating specific areas of your program without impacting the remainder.
Best Practice 3: Regularly Update Dependencies
Performance concerns, compatibility difficulties, and security risks might result from outdated dependencies. Update your dependencies often to guarantee that your program takes advantage of the newest additions and enhancements.
Best Practice 4: Test Often
To make sure your app functions as intended on all platforms and devices, testing is essential. Write integration tests for user flows and unit tests for individual components. When testing, if feasible, use real devices to identify problems that may not show up in simulations.
Best Practice 5: Monitoring Performance
For a user experience to be seamless, performance is essential. Utilize resources like as Flipper and React Developer Tools to track the functionality of your project and pinpoint areas in need of development. Optimize your code to decrease battery depletion, increase load times, and use less memory.
Best Practice 6: Document Your Code
It is simpler for you and other people to comprehend your code when there is good documentation. For intricate parts, modules, and services, jot down concise remarks and provide documentation. When bringing on new team members or reviewing your code after a lengthy absence, this will save time.
Conclusion
Although creating a React Native app without Expo takes more time and technical expertise, you have more control over the features and architecture of your project.
You’ll be well-equipped to build a strong and effective React Native app that meets your unique requirements if you follow this course.
Remember to stay organized, keep your dependencies up-to-date, test your app thoroughly, and hire a mobile app development company like DianApps to ensure the best possible user experience.