DEV Community

Cover image for Fundamentals of React Native App Development: Dependencies, Performance, Native Modules, and Publishing Guide
abdulnasır olcan
abdulnasır olcan

Posted on

Fundamentals of React Native App Development: Dependencies, Performance, Native Modules, and Publishing Guide

React Native is a powerful tool that allows you to develop both iOS and Android applications with a single codebase. However, a successful app development process is not just about using components. You also need to master several core topics, such as dependency management, performance optimization, data management, form validation, native modules, and finally, the app publishing process.

Building on the Basics: In addition to React Native's basic features like useState, useEffect, useMemo, useCallback, and the Context API, you should also be proficient in more complex structures such as Redux, React Navigation, AsyncStorage, MMKV, and performance optimization techniques.

Native Modules and Bridging: React Native may not always be sufficient; therefore, when necessary, you should be able to write native modules with Java (for Android) and Objective-C/Swift (for iOS).

Performance Management: To control the re-rendering of components, hooks like memo, useCallback, and useMemo should be effectively utilized. Additionally, optimizations should be considered when working with lists, such as using FlatList.

Animations: To ensure smooth and high-performance animations in React Native, libraries like Reanimated and Gesture Handler should be well understood.

App Startup Time: Techniques for shortening the app's startup time and loading large modules using lazy loading should be known.

Redux / Context API / React Query: In large-scale projects, global state management solutions like Redux or React Query should be handled, while smaller projects may benefit from using the Context API.

State Persistency: In cases where data needs to be continuously stored, state management should be maintained with tools like Redux Persist, MMKV, or AsyncStorage.

In this article, I will also dive into tools and libraries such as Watchman, Gemfile, Metro, react-native-gesture-handler, react-native-gradle-plugin, react-native-mmkv, react-native-webview, @tanstack/react-query, Axios, Ky, react-hook-form, Zod, and TypeScript, explaining them in detail with examples.

1. React Native Development Environment Setup

a. What is Watchman?

Watchman is a tool developed by Facebook that monitors file changes and triggers specific commands when those changes occur. In React Native projects, it is used to enable automatic reload (hot-reload). When you make changes to a file, Watchman detects the modification and updates the project accordingly.



brew install watchman


Enter fullscreen mode Exit fullscreen mode

How Does Watchman Work?

Watchman monitors file system changes and automatically restarts the project based on these changes. This speeds up the development process and eliminates the need for manual restarts.

b. What is a Gemfile?

A Gemfile is a file used to manage Ruby dependencies. You can use a Gemfile to integrate tools like Fastlane and Cocoapods into your project. This provides fast and efficient dependency management, especially for iOS applications.



source "https://rubygems.org"

gem "fastlane"
gem "cocoapods"


Enter fullscreen mode Exit fullscreen mode

This file allows you to install the correct versions of Fastlane and Cocoapods. By running the bundle install command, you can add these dependencies to your project.

c. What is Metro?

Metro is React Native’s bundler tool. It combines all your JavaScript code into a single file and optimizes it. Metro compiles project code quickly and provides advanced error reporting.

Role of Metro:

  • Hot Reloading: Reflects code changes instantly in the app.
  • Code Bundling: Combines JavaScript files to improve performance.
  • Error Reporting: Makes errors more readable during development.

Metro enhances the performance of React Native projects and speeds up the development process.

2. Dependency Management in React Native: Working with Cocoapods and Gradle

Dependency management is important for both platforms in React Native projects: Cocoapods for iOS and Gradle for Android.

What is Cocoapods and How is it Used?

Cocoapods provides dependency management for iOS applications. You can add third-party libraries to your project by adding the necessary dependencies to the Podfile and running the pod install command.

Sample Podfile:



platform :ios, '12.0'

target 'YourApp' do
  pod 'Firebase', '~> 6.0'
  pod 'ReactNativeGestureHandler', :path => '../node_modules/react-native-gesture-handler'
end


Enter fullscreen mode Exit fullscreen mode

Installation Command:



pod install


Enter fullscreen mode Exit fullscreen mode

Cocoapods makes it easy to add and manage third-party libraries in iOS projects. It provides version control for any library and manages in-app dependencies.

What is Gradle and How is it Used in Android Projects?

Gradle is the dependency management and build system for Android projects. The react-native-gradle-plugin simplifies and optimizes these processes. In Android projects, you can define and include third-party libraries using the build.gradle file.

Example build.gradle:



dependencies {
    implementation 'com.google.firebase:firebase-analytics:17.2.0'
    implementation 'com.facebook.react:react-native:+'
}


Enter fullscreen mode Exit fullscreen mode

The react-native-gradle-plugin adapts Gradle for React Native projects and speeds up build processes. It also provides the necessary settings to ensure the proper signing and optimization of the application.

3. Native Modules and Bridging

React Native handles many tasks using JavaScript; however, in some cases, you may need to write native modules. Particularly for platform-specific tasks (like Bluetooth, NFC, or camera access), native modules are written on the Android (Java) and iOS (Objective-C/Swift) sides and bridged with React Native.

a. Creating a Native Module: Android

Java is used to create a native module for Android. For example, to write a simple module that fetches the battery level of an Android device, you can follow these steps:

Step 1: Creating a Java File

To create a native module for Android, create a Java file at the path android/app/src/main/java/com/yourproject/. In this example, the file name will be BatteryModule.java.



package com.yourproject;

import android.os.BatteryManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;

public class BatteryModule extends ReactContextBaseJavaModule {
  private static ReactApplicationContext reactContext;

  BatteryModule(ReactApplicationContext context) {
    super(context);
    reactContext = context;
  }

  @NonNull
  @Override
  public String getName() {
    return "BatteryModule";
  }

  @ReactMethod
  public void getBatteryLevel(Promise promise) {
    try {
      Intent batteryIntent = reactContext.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
      int level = batteryIntent != null ? batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) : -1;
      int scale = batteryIntent != null ? batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, -1) : -1;
      float batteryLevel = level / (float) scale;
      promise.resolve(batteryLevel);
    } catch (Exception e) {
      promise.reject("Error", e);
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

Explanation:

We have created a Java class named BatteryModule, and this class will be bridged to React Native as a native module.
We defined a method called getBatteryLevel. This method retrieves the battery level of the device and returns it to the React Native side through a Promise.

Step 2: Registering the Module with React Native

Go to the MainApplication.java file and register the native module with React Native.



import com.yourproject.BatteryModule;

public class MainApplication extends Application implements ReactApplication {

  @Override
  protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
      new MainReactPackage(),
      new BatteryPackage()
    );
  }
}


Enter fullscreen mode Exit fullscreen mode

To create the module package, we add a class named



package com.yourproject;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class BatteryPackage implements ReactPackage {

  @Override
  public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
    List<NativeModule> modules = new ArrayList<>();
    modules.add(new BatteryModule(reactContext));
    return modules;
  }

  @Override
  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
    return Collections.emptyList();
  }
}


Enter fullscreen mode Exit fullscreen mode

Explanation:

BatteryPackage is used to link the BatteryModule class to React Native. By adding this package to MainApplication, the bridging process is completed.

b. Creating a Native Module: iOS

On the iOS side, you can write a native module using Swift or Objective-C. For example, to retrieve the battery level of iOS devices, you can use the following code:

Step 1: Creating a Swift File

In Xcode, we create a Swift file named BatteryModule.swift.



import Foundation
import UIKit

@objc(BatteryModule)
class BatteryModule: NSObject {

  @objc
  func getBatteryLevel(_ resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
    UIDevice.current.isBatteryMonitoringEnabled = true
    let batteryLevel = UIDevice.current.batteryLevel
    if batteryLevel >= 0 {
      resolve(batteryLevel)
    } else {
      let error = NSError(domain: "", code: 200, userInfo: nil)
      reject("no_battery_info", "Could not fetch battery level", error)
    }
  }

  @objc
  static func requiresMainQueueSetup() -> Bool {
    return false
  }
}


Enter fullscreen mode Exit fullscreen mode

Explanation:

We create a class named BatteryModule. This class retrieves the device's battery level.
The getBatteryLevel method returns the battery level using a Promise.

Step 2: Bridging the Module

To bridge BatteryModule to React Native, we create an Objective-C bridging file named BatteryModule.m:



#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(BatteryModule, NSObject)

RCT_EXTERN_METHOD(getBatteryLevel:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)

@end


Enter fullscreen mode Exit fullscreen mode

This is used to bridge the module we wrote in Swift to React Native.

c. Usage on the React Native Side

Now that we have written and bridged the native modules for both Android and iOS, we can use these modules on the React Native side as follows:



import { NativeModules, Button, Text, View } from 'react-native';
import React, { useState } from 'react';

const { BatteryModule } = NativeModules;

const App = () => {
  const [batteryLevel, setBatteryLevel] = useState(null);

  const getBatteryLevel = async () => {
    try {
      const level = await BatteryModule.getBatteryLevel();
      setBatteryLevel(level);
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <View>
      <Text>Battery Level: {batteryLevel !== null ? ${batteryLevel * 100}% : N/A}</Text>
      <Button title="Get Battery Level" onPress={getBatteryLevel} />
    </View>
  );
};

export default App;


Enter fullscreen mode Exit fullscreen mode

Explanation:

By calling the BatteryModule.getBatteryLevel() function, we retrieve the device's battery level and display it on the screen using useState.
With this method, you can add platform-specific features to React Native projects. Native modules provide more direct access to device hardware and can help improve the performance of your projects.

4. Performance Optimization: Developing Faster Applications

In React Native projects, performance is a critical concern. Ensuring that your application is fast and smooth for users is one of the key factors for a successful mobile application. You can use the following optimization methods to enhance performance in large and complex projects:

a. Optimized Listings with FlatList

If your application manages large data lists, using FlatList can significantly improve performance. FlatList only renders the items visible on the screen, which reduces unnecessary memory usage.



<FlatList
  data={data}
  renderItem={({ item }) => <Text>{item.title}</Text>}
  keyExtractor={item => item.id}
/>


Enter fullscreen mode Exit fullscreen mode

Additionally, if you want to use a faster and more performant FlatList, you can check out this library:

b. Preventing Unnecessary Renders with Memoization and useCallback

In React Native projects, you can use the useMemo and useCallback hooks to prevent unnecessary renders, especially in large components. These hooks prevent components from re-rendering unless certain changes occur.



const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);


Enter fullscreen mode Exit fullscreen mode

These methods especially improve performance on screens with many components, making your application respond faster.

c. Performant Animations with Reanimated and Gesture Handler

In React Native, animations can sometimes lead to performance issues. Therefore, libraries like React Native Reanimated and Gesture Handler are used for high-performance animations and gestures.



import { useSharedValue, withSpring } from 'react-native-reanimated';

const progress = useSharedValue(0);

progress.value = withSpring(1);


Enter fullscreen mode Exit fullscreen mode

These libraries ensure that performance is maintained, especially when working with heavy animations and gestures.

5. Performance Optimization: High-Performance Storage with MMKV

When performing data storage operations in your application, you need a high-performance storage mechanism. react-native-mmkv, developed by Facebook, is an optimized storage solution for React Native. MMKV allows you to handle large data sets quickly.

How to Use MMKV?

Installation:

Example: Storing and Retrieving User Data

To store and later retrieve simple user data using MMKV, you can follow these steps:



import { MMKV } from 'react-native-mmkv';

const userData = {
  id: 1,
  name: "Abdulnasır olcan",
  email: "abdulnasir.olcan@example.com",
  isLoggedIn: true,
};

const storage = new MMKV();

storage.set('user', JSON.stringify(userData));

const userString = storage.getString('user');

if (userString) {
  const user = JSON.parse(userString);
  console.log('user:', user);
} else {
  console.log('Error');
}


Enter fullscreen mode Exit fullscreen mode

Key Points:

  • Data Saving: We save the data using storage.set('key', value). Here, we store the user data (userData) in JSON format under the user key.
  • Data Reading: We retrieve the data using storage.getString('key'). To convert the data back to JSON format, we use JSON.parse().
  • Error Handling: When fetching the data, if the data is empty (for example, if it was never saved or has been deleted), it will return null. Therefore, we check whether the data exists or not.

Example: Updating and Deleting Data

Updating or deleting data stored in MMKV is also very simple. You can use the examples below for updating and deleting data.

Updating Data:



const updatedUserData = {
  ...userData,
  name: "Abdulnasır olcan",
};

storage.set('user', JSON.stringify(updatedUserData));

const updatedUserString = storage.getString('user');
if (updatedUserString) {
  const updatedUser = JSON.parse(updatedUserString);
  console.log('update user:', updatedUser);
}


Enter fullscreen mode Exit fullscreen mode

Example: Deleting Data



// Depolanan kullanıcı verisini siliyoruz
storage.delete('user');

// Sildiğimiz veriyi kontrol ediyoruz
const deletedUserString = storage.getString('user');
if (!deletedUserString) {
  console.log('Kullanıcı verisi başarıyla silindi.');
}


Enter fullscreen mode Exit fullscreen mode

MMKV is a fast and secure local data storage solution used to enhance your application's performance. It allows you to quickly store user data, session information, or other important information.

6. Integration with Web Content: react-native-webview

You can use the react-native-webview component to display web content in your application. This component renders web pages using the native browser engine.

How to Use WebView?

Installation:



npm install react-native-webview


Enter fullscreen mode Exit fullscreen mode

Example Usage:



import { WebView } from 'react-native-webview';

const MyWeb = () => (
  <WebView source={{ uri: 'https://reactnative.dev/' }} />
);


Enter fullscreen mode Exit fullscreen mode

WebView allows you to easily display external content that you want to integrate into your application. This is especially useful for in-app payment pages or viewing external documents.

7. Data Management: Data Fetching with @tanstack/react-query

When your application exchanges data with APIs, you can simplify data fetching and caching by using @tanstack/react-query. React Query manages API requests and provides automatic retry and caching functionalities.

Fetching Data with React Query:

Installation:



npm install @tanstack/react-query


Enter fullscreen mode Exit fullscreen mode

Example Usage:



import { useQuery } from '@tanstack/react-query';
import axios from 'axios';

const fetchData = async () => {
  const { data } = await axios.get('https://api.example.com/data');
  return data;
};

const MyComponent = () => {
  const { data, isLoading } = useQuery(['data'], fetchData);

  if (isLoading) return <Text>Loading...</Text>;
  return <Text>{data.title}</Text>;
};


Enter fullscreen mode Exit fullscreen mode

React Query allows you to manage data requests more effectively. Additionally, with react-query-devtools, you can visually monitor and analyze the state of your queries.

8. Navigation: Screen Transitions with react-navigation

In React Native applications, react-navigation is used for transitioning between screens. It is ideal for navigating between multiple screens, opening modals, and handling stacked navigation.

Using react-navigation:

Installation:



npm install @react-navigation/native
npm install @react-navigation/stack


Enter fullscreen mode Exit fullscreen mode

Example Usage:



import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

const Stack = createStackNavigator();

const MyStack = () => (
  <NavigationContainer>
    <Stack.Navigator>
      <Stack.Screen name="Home" component={HomeScreen} />
      <Stack.Screen name="Details" component={DetailsScreen} />
    </Stack.Navigator>
  </NavigationContainer>
);


Enter fullscreen mode Exit fullscreen mode

Navigation is a critical component in modern mobile applications, and react-navigation is one of the most commonly used solutions for screen transitions.

9. API Requests: Fetching Data with Axios and Ky

The two most commonly used libraries for managing API requests are Axios and Ky. Both provide simple and efficient solutions for making HTTP requests.

Making an API Request with Axios:

Installation:



npm install axios


Enter fullscreen mode Exit fullscreen mode

Example Usage:



axios.get('https://api.example.com/data')
  .then(response => console.log(response.data))
  .catch(error => console.error(error));


Enter fullscreen mode Exit fullscreen mode

Making an API Request with Ky:

Installation:



npm install ky


Enter fullscreen mode Exit fullscreen mode

Example Usage:



import ky from 'ky';

ky.get('https://api.example.com/data').json()
  .then(data => console.log(data));


Enter fullscreen mode Exit fullscreen mode

Both libraries can be used for data exchange with APIs, and each offers different advantages. Axios provides a broader API and more flexibility, while Ky is lighter and has a more modern structure.

10. Form Management and Validation: Powerful Forms with react-hook-form and Zod

For form management and validation, react-hook-form and Zod can be used. These tools allow you to manage form data more securely and flexibly.

Form Management with react-hook-form:

Installation:



npm install react-hook-form


Enter fullscreen mode Exit fullscreen mode

Example Usage:



import { useForm } from 'react-hook-form';

const MyForm = () => {
  const { register, handleSubmit } = useForm();
  const onSubmit = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name')} placeholder="Name" />
      <input type="submit" />
    </form>
  );
};


Enter fullscreen mode Exit fullscreen mode

Form Validation with Zod:

Installation:



npm install zod


Enter fullscreen mode Exit fullscreen mode

Example Usage:



import { z } from 'zod';

const schema = z.object({
  name: z.string().min(2, 'Name must be at least 2 characters long'),
});

schema.parse({ name: 'Abdulnasır olcan' });
schema.parse({ name: 'A' });


Enter fullscreen mode Exit fullscreen mode

While react-hook-form is optimized for managing and validating forms, Zod makes form validation even more powerful.

11. Strong Type Checking with TypeScript

TypeScript is a programming language built on top of JavaScript, providing static type checking. By using TypeScript in React Native projects, you can achieve a safer and more error-free development process.

React Native with TypeScript:

Installation:



npx react-native init MyApp --template react-native-template-typescript


Enter fullscreen mode Exit fullscreen mode

Example Usage:



type User = {
  id: number;
  name: string;
};

const getUser = (user: User): string => {
  return User: ${user.name};
};


Enter fullscreen mode Exit fullscreen mode

TypeScript helps identify errors early, especially in large projects, and makes your code more maintainable.

12. Debugging and Performance Monitoring with Reactotron

Reactotron is a powerful tool that simplifies debugging, monitoring API requests, and tracking performance in React Native projects. With Reactotron, you can monitor your application's performance and quickly resolve issues.

Reactotron Setup:

Installation:



npm install reactotron-react-native


Enter fullscreen mode Exit fullscreen mode

Example Usage:



// queryClientConfig.js

import { QueryClient } from '@tanstack/react-query';

// Initialize Query Client
export const queryClient = new QueryClient();


Enter fullscreen mode Exit fullscreen mode


// ReactotronConfig.js

import Reactotron from 'reactotron-react-native';
import mmkvPlugin from 'reactotron-react-native-mmkv';
import {
  QueryClientManager,
  reactotronReactQuery,
} from 'reactotron-react-query';
import config from './app.json';
import { queryClient } from './queryClientConfig';
import { MMKV } from 'react-native-mmkv';

// Initialize MMKV Storage
export const storage = new MMKV();

const queryClientManager = new QueryClientManager({
  queryClient,
});

// Configure Reactotron
Reactotron.configure({
  name: config.name,
  onDisconnect: () => {
    queryClientManager.unsubscribe();
  },
})
  .useReactNative()
  .use(mmkvPlugin({ storage })) // Use the MMKV plugin to store the Reactotron state
  .use(reactotronReactQuery(queryClientManager)) // Use the react-query plugin to store the Reactotron state
  .connect();


Enter fullscreen mode Exit fullscreen mode

With Reactotron desktop, you can view the actions performed using react-native-mmkv and @tanstack/react-query.

Reactotron allows you to quickly identify issues in your project by monitoring API requests and error states through the developer console.

13. App Deployment: Automated Distribution with Fastlane

You can automate the process of publishing your React Native app on the App Store and Google Play using Fastlane. This tool automates tasks such as signing, versioning, and uploading the app to the store.

App Store and Google Play Process

Create an Android App Bundle (AAB):



cd android
./gradlew bundleRelease


Enter fullscreen mode Exit fullscreen mode

This command compiles your project for Android and generates the AAB file.

Create a new app in the Google Play Console and upload your AAB file.

Fill in the app details, add screenshots and icons. Wait for your app to be published.

Uploading an App to the App Store

  • Archive your app via Xcode.
  • Go to App Store Connect and create a new app record.
  • Add the necessary information such as app icon, description, and screenshots.
  • Submit your app to the App Store and wait for Apple’s review process.

Deployment with Fastlane

Installation:



sudo gem install fastlane -NV


Enter fullscreen mode Exit fullscreen mode

Example Usage Fastfile:



platform :ios do
  desc "App Store'a yükle"
  lane :deploy_to_appstore do
    increment_version_number
    build_app(scheme: "YourApp")
    upload_to_app_store
  end
end

platform :android do
  desc "Google Play'e yükle"
  lane :deploy_to_playstore do
    increment_version_code
    gradle(task: "bundleRelease")
    upload_to_play_store
  end
end


Enter fullscreen mode Exit fullscreen mode

Fastlane allows you to upload your app to the stores without performing manual tasks each time you version your application.

Conclusion

React Native offers a revolutionary platform for modern mobile application development with its powerful and flexible structure. In this article, we covered the most essential tools and libraries you can use when developing applications with React Native. Mastering topics like dependency management, performance optimization, data management, form validation, TypeScript integration, and debugging tools will enable you to develop your projects more efficiently and securely. Happy coding! 🚀

Top comments (0)