Create Native Android Module with Kotlin and Native Ios Module with Swift and use them in React Native
Malik Chohra
React Native Mobile Engineer | Sharing Insights on Software Engineering | Digital Nomad Lifestyle | Passionate about Team Collaboration, processes and modern technology
INTRODUCTION
This article contains how to write Native Android and Native IOS modules and use them in a React native app. I have written 4 parts in Medium and I am putting all of that in this big article .
This is the source code in Github: https://github.com/chohra-med/rnWidget
If you prefer to read in Medium, this is the structure:
All the articles are here:
1- First Step: Understand React Native: https://medium.com/@malikchohra/build-for-scale-create-native-android-and-ios-module-in-react-native-prepare-react-native-app-09d615ba2450
2- Implement IOS Native Module : https://medium.com/@malikchohra/create-native-android-and-ios-module-in-react-native-use-widget-in-native-ios-24e227050654
3 Implement Native Android Module: https://medium.com/@malikchohra/create-native-android-and-ios-module-in-react-native-use-widget-in-native-android-6e2f4a09d263
4- Put all together and Link to Github project https://medium.com/@malikchohra/build-for-scale-create-a-native-android-and-ios-module-in-react-native-put-all-the-steps-93af014325bf
Otherwise, let's start:
App Application through for this example
This tutorial demonstrates how to build a versatile widget app using React Native and native modules. Our app will fetch random phrases from a predefined set and enable users to submit new ones, which will be instantly displayed on both Android and iOS widgets.
Throughout this guide, you’ll gain a comprehensive understanding of:
###FIRST STEP: Preparing the React Native side
React Native is a popular framework developed by Meta that enables developers to build mobile applications using JavaScript and React, a powerful library for building user interfaces.
The key advantage of React Native lies in its ability to bridge the gap between web and mobile development by allowing developers to write code in JavaScript, while still harnessing the power of native components and functionalities on both iOS and Android. This approach ensures that mobile apps retain the performance and feel of truly native applications, all while streamlining the development process with a single codebase
Consider this simple example:
import React from 'react';
import { View, Text, Platform } from 'react-native';
const WelcomeComponent = () => {
return (
<View>
<Text>
Welcome to {Platform.OS === 'ios' ? 'iOS' : 'Android'}!
</Text>
</View>
);
};
This code automatically renders platform-specific native components: UIView and UILabel on iOS, and android.view.View and android.widget.TextView on Android.
React Native boasts one of the largest and most active developer communities in the mobile development sphere. This vibrant ecosystem is evident through the following:
Despite the rich ecosystem, there are scenarios where the existing JavaScript APIs and community packages don’t suffice. Common situations include:
Creating Native module:
When working with React Native, the underlying mechanism that enables the integration of native modules is the “bridge” architecture. This bridge allows for communication between the JavaScript code and the native code on both iOS and Android platforms. Here’s how it works in detail:
How React Native Works Under the Hood
When you build a React Native app, the JavaScript code runs inside a JavaScript engine (usually Hermes or JSC). This JavaScript code does not interact directly with the native platform (Android or iOS) but communicates through a bridge, which serves as a translator between the JavaScript code and the native modules written in Java (for Android) or Swift/Objective-C (for iOS).
To implement a native module, you need to create platform-specific code for both iOS and Android and then export these modules to the React Native side. The JavaScript code can then invoke the native module’s functions as if they were ordinary JavaScript functions, thanks to the bridge’s translation.
For example, to create a native module for both iOS and Android:
The native modules are then registered with the React Native runtime, allowing them to be accessed from JavaScript
Bi-Directional Communication Between JavaScript and Native Code
The React Native bridge supports bi-directional communication, meaning that data can flow from JavaScript to native code and vice versa.
JavaScript to Native Communication:
When you call a function on a native module, the request goes through the bridge. For example, invoking NativeModules.AppMessage.updateScreenMessage("Hello from React Native!") sends the string "Hello from React Native!" to the native module on the respective platform, which then performs the desired functionality (such as updating a widget).
Native to JavaScript Communication:
Conversely, native code can also invoke functions in the JavaScript realm. This is typically done by emitting events.
Event Handling Across JavaScript and Native
Each action or event that occurs in JavaScript can correspond to one in the native code, and vice versa. For instance:
This communication is facilitated by event emitters on both platforms:
How Native Modules Are Exposed to JavaScript
Step 1 — Creating the Module:
You write the native code separately for Android (Java/Kotlin) and iOS (Swift/Objective-C).
Step 2 — Exporting the Module:
You implement the respective module methods and register the modules with the React Native framework.
Step 3 — Registering the Module in JavaScript:
and React Native can handle it automatically.
Our App will have the following Diagram:
To update a widget from a React Native app:
Both native implementations perform the actual widget update in their respective platform’s way, but from the perspective of the JavaScript code, it’s a seamless call.
Let’s go to the Implementation in React Native
step 1: create the const that has all the words that we randomize into
export const motivationalPhrases: string[] = [
"Believe you can and you're halfway there.",
"Success is not final, failure is not fatal: it is the courage to continue that counts.",
"The only way to do great work is to love what you do.",
"Strive not to be a success, but rather to be of value.",
"The future belongs to those who believe in the beauty of their dreams.",
"Don't watch the clock; do what it does. Keep going.",
"The secret of getting ahead is getting started.",
"Your time is limited, don't waste it living someone else's life.",
"The only limit to our realization of tomorrow will be our doubts of today.",
"It always seems impossible until it's done.",
"The harder you work for something, the greater you'll feel when you achieve it.",
"Dream big and dare to fail.",
"Success is not in what you have, but who you are.",
"The only person you are destined to become is the person you decide to be.",
"Believe in yourself, take on your challenges, dig deep within yourself to conquer fears."
];
step 2: create the hook to manage that, and that passes by the Native module
import {useState, useCallback, useEffect} from 'react';
import {NativeModules, Platform} from 'react-native';
import {motivationalPhrases} from '../data/motivationalPhrases';
// we call the Native Module
const {WidgetModule} = NativeModules;
export const useRandomPhrase = () => {
const [currentPhrase, setCurrentPhrase] = useState<string>('');
const selectRandomPhrase = useCallback(() => {
const randomIndex = Math.floor(Math.random() * motivationalPhrases.length);
const newPhrase = motivationalPhrases[randomIndex];
setCurrentPhrase(newPhrase);
// Update the widget with the new phrase
if (Platform.OS === 'android' || Platform.OS === 'ios') {
console.log('Updating widget with new phrase:', newPhrase);
console.log('WidgetModule:', WidgetModule);
try {
WidgetModule.updateWidget(newPhrase);
} catch (e) {
console.log(e);
}
}
}, []);
useEffect(() => {
selectRandomPhrase();
}, []);
return {currentPhrase, selectRandomPhrase};
};
Conclusion: Best Practices for Creating Native Modules in React Native
Performance Considerations
Memory Management
Error Handling Patterns
Documentation Standards
Testing Native Modules
###STEP 2: Create IOS Natiev Module:
Introduction
To write an article about creating a native module in Ios using Swift and integrating it with React Native, we should first provide the reader with a solid understanding of how native Ios development works. This includes explaining key concepts, tools, and best practices that a React Native developer should know before diving into creating native modules
Key iOS Components
When working with native modules in iOS, it’s essential to understand some core iOS concepts:
1. View Controllers:
2. Views:
3. App Delegates:
4. Extensions:
5. Frameworks:
6. UIKit
let button = UIButton(type: .system)
button.setTitle("Click me", for: .normal)
7. AppKit
8. App Groups and Data Sharing in iOS
App Groups allow multiple apps from the same developer to share data and resources. This is particularly useful for creating interconnected apps that work together seamlessly.
How to implement Widget Module in Native IOS:
Implementing a widget in iOS using a React Native codebase involves several steps. Here’s a step-by-step guide to help you integrate a widget into your iOS app:
Step 1: Set Up Your iOS Project for Widgets
1. Open Your iOS Project in Xcode:
2. Add a Widget Target:
3. Configure App Groups:
Step 2: Implement the Widget Logic
1. Edit the Widget Code:
2. Access Shared Data:
3. Update Widget Content:
Step 3: Connect React Native with the Widget
1. Bridge React Native and Native Code:
领英推荐
2. Trigger Widget Updates:
struct Provider: AppIntentTimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), configuration: ConfigurationAppIntent())
}
func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> SimpleEntry {
SimpleEntry(date: Date(), configuration: configuration)
}
func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline<SimpleEntry> {
var entries: [SimpleEntry] = []
// access to the shared group value
let shared = UserDefaults(suiteName: "group.org.reactjs.native.example.aiWidgetRN.WidgetMotivation")
let phrase = shared?.string(forKey: "phrase") ?? "Not found"
// Define entryDate as now
let entryDate = Date()
configuration.motivationalPhrase = phrase
let entry = SimpleEntry(date: entryDate, configuration: configuration)
entries.append(entry)
return Timeline(entries: entries, policy: .atEnd)
}
}
The code is:
#import "WidgetModuleApp.h"
#import <React/RCTLog.h>
#import "aiWidgetRN-Swift.h"
@implementation WidgetModuleApp
RCT_EXPORT_MODULE(WidgetModule);
RCT_EXPORT_METHOD(updateWidget:(NSString *)phrase)
{
@try{
NSUserDefaults *shared = [[NSUserDefaults alloc]initWithSuiteName:@"group.org.reactjs.native.example.aiWidgetRN.WidgetMotivation"];
[shared setObject:phrase forKey:@"phrase"];
[shared synchronize];
// Reload widget content
// Import the Swift bridging header
[WidgetController reloadWidgetContent];
//
}@catch(NSException *exception){
RCTLogInfo(@" widget error at %@", exception);
}
}
@end
Step 4: Test Your Widget
1. Build and Run:
Best Practices:
Native module Testing
You need to integrate Unit testing in your swift module, for example
#import <XCTest/XCTest.h>
#import "WidgetModuleApp.h"
@interface WidgetModuleAppTests : XCTestCase
@property (nonatomic, strong) WidgetModuleApp *widgetModule;
@end
@implementation WidgetModuleAppTests
- (void)setUp {
[super setUp];
self.widgetModule = [[WidgetModuleApp alloc] init];
}
- (void)tearDown {
self.widgetModule = nil;
[super tearDown];
}
- (void)testUpdateWidgetStoresPhrase {
NSString *testPhrase = @"Test Phrase";
// Call the method to test
[self.widgetModule updateWidget:testPhrase];
// Retrieve the stored phrase from UserDefaults
NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.org.reactjs.native.example.aiWidgetRN.WidgetMotivation"];
NSString *storedPhrase = [shared stringForKey:@"phrase"];
// Assert that the stored phrase matches the test phrase
XCTAssertEqualObjects(storedPhrase, testPhrase, @"The stored phrase should match the test phrase.");
}
- (void)testUpdateWidgetWithEmptyString {
NSString *emptyPhrase = @"";
// Call the method to test
[self.widgetModule updateWidget:emptyPhrase];
// Retrieve the stored phrase from UserDefaults
NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.org.reactjs.native.example.aiWidgetRN.WidgetMotivation"];
NSString *storedPhrase = [shared stringForKey:@"phrase"];
// Assert that the stored phrase matches the empty string
XCTAssertEqualObjects(storedPhrase, emptyPhrase, @"The stored phrase should match the empty string.");
}
- (void)testUpdateWidgetWithNil {
// Call the method to test with nil
[self.widgetModule updateWidget:nil];
// Retrieve the stored phrase from UserDefaults
NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.org.reactjs.native.example.aiWidgetRN.WidgetMotivation"];
NSString *storedPhrase = [shared stringForKey:@"phrase"];
// Assert that the stored phrase is nil
XCTAssertNil(storedPhrase, @"The stored phrase should be nil when updated with nil.");
}
@end
Native Module Error handling
Add Error handling to your modules in case of a problem with anything that happens in the app, it won’t crash it
Architect your Modules
Go for an architect to keep your code clean, MVVM < we already discussed it here> can be really helpful.
The App Review in Ios:
##Part 3: Create Android Module
Understanding Native Android Basics
Android Architecture Overview
Android applications are built on a component-based architecture. Before diving into widget development, let’s understand the core components:
// Basic Android Component Example
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Key Android Components:
Services are used for background processing in Android. They can run operations even when the user is not interacting with the app, such as playing music, fetching data, or processing files
Listen for system-wide events or custom app broadcasts, such as connectivity changes
Manage shared app data and make it accessible to other apps.
In React Native, sharing data is usually managed via state management libraries like Redux
Kotlin and Java Development
Kotlin, different concepts make it favorable nowadays in Native Android development:
Displaying Interfaces (UI Handling)
Android uses activities and fragments to represent screens or parts of a screen.
Activities:
Fragments:
Handling Functions and Events
In Android, functions are typically methods defined within classes. Events are handled using listeners or callbacks.
It is equivalent in React Native: JavaScript functions and event listeners (e.g., onPress for buttons).
Using Intents for Communication
Intents are messages that allow different components of an Android app to communicate with each other.
In React Native, communication between components is managed through props, navigation parameters, or native modules.
// Explicit Intent
val intent = Intent(context, TargetActivity::class.java).apply {
putExtra("key", "value")
}
startActivity(intent)
// Broadcast Intent
val intent = Intent("WIDGET_UPDATE_ACTION").apply {
putExtra("message", "Update Widget")
}
context.sendBroadcast(intent)
Best Practices with MVVM Architecture
MVVM (Model-View-ViewModel) is a popular architectural pattern for Android development. It separates concerns into:
Benefits of MVVM:
Let’s Start the Widget Native Android Implementation
Step 1: Create the widget on Android
step 2: define the interface for the widget
- In your Android project, navigate to res/layout and create a new XML layout file for your widget (e.g., widget_layout.xml).
create the widget layout for your widget in XML:
<!-- widget_layout.xml -->
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#09C"
android:padding="@dimen/widget_margin">
<TextView
android:id="@+id/appwidget_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_margin="8dp"
android:background="#09C"
android:contentDescription="@string/appwidget_text"
android:text="@string/appwidget_text"
android:textColor="#ffffff"
android:textSize="24sp"
android:textStyle="bold|italic" />
</RelativeLayout>
step 3: create the widget module to handle the update
To handle the widget
package com.aiwidgetrn
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Intent
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import android.util.Log
import com.facebook.react.bridge.ReactApplicationContext
class WidgetModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
override fun getName() = "WidgetModule"
// Holds the current phrase to be displayed on the widget
private var currentPhrase: String = "Believe in yourself!"
@ReactMethod
fun setPhrase(phrase: String) {
// Update the current phrase
currentPhrase = phrase
// Existing code to update widget...
}
// Function to get the current phrase
fun getCurrentPhrase(): String {
return currentPhrase
}
@ReactMethod
fun updateWidget(phrase: String) {
Log.d("Widget Module", "updateWidget function called")
// Update the current phrase
currentPhrase = phrase
// Get the application context
val context = reactApplicationContext.applicationContext
// Create an intent to update the widget
val intent = Intent(context, MotivationalWidgetImplementation::class.java)
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
// Get the widget IDs
val ids = AppWidgetManager.getInstance(context)
.getAppWidgetIds(ComponentName(context, MotivationalWidgetImplementation::class.java))
// Add widget IDs to the intent
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
Log.d("New Phrase", "Updating widget with phrase: $phrase")
// Send the broadcast to update the widget
context.sendBroadcast(intent)
// Update the widget text with the new phrase
MotivationalWidgetImplementation.updateWidgetText(context, currentPhrase)
}
}
step 4: integrate it as a React native Export module
We need to create the code that we import
// WidgetPackage.kt
class WidgetPackage : ReactPackage {
override fun createNativeModules(
reactContext: ReactApplicationContext
): List<NativeModule> {
return listOf(WidgetModule(reactContext))
}
override fun createViewManagers(
reactContext: ReactApplicationContext
): List<ViewManager<*, *>> {
return emptyList()
}
and add it to our MainApplication.kt
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(WidgetPackage())
add(WidgetPackage())
}
step 5: register it to Android Manifest
//.....
// AndroidManifest.xml
<receiver
android:name=".MotivationalWidgetImplementation"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/motivational_widget_implementation_info" />
</receiver>
step 6: react native side
2. Trigger Widget Updates:
Best Practices:
1- Add Testing:
@RunWith(AndroidJUnit4::class)
class WidgetTests {
@Test
fun testWidgetUpdate() {
val context = InstrumentationRegistry.getInstrumentation().context
val message = "Test Message"
// Test widget update
val result = updateWidgetSafely(context, message)
assertTrue(result.isSuccess)
// Verify stored state
assertEquals(message, WidgetStateManager.getMessage(context))
}
}
2- Error Handling:
sealed class WidgetError : Exception() {
class UpdateFailed(override val message: String) : WidgetError()
class InvalidState(override val message: String) : WidgetError()
}
fun updateWidgetSafely(context: Context, message: String): Result<Unit> {
return try {
// Update implementation
Result.success(Unit)
} catch (e: Exception) {
Result.failure(WidgetError.UpdateFailed(e.message ?: "Unknown error"))
}
}
3- MVVM architecture:
We have already implemented that
The App Review in Android:
###Part 4: Put everything together:
What we did In a nutshell:
This tutorial is part of a series where we progressively build up skills. Here’s a quick summary:
1- The Basics of React Native: In the first article, we explored how React Native works under the hood, introduced the app we’ll be building on, and outlined the steps we’d take. As a hands-on start, we also implemented a feature to generate a random string in React Native.
2- Understanding Native Modules: Next, we went over the core concepts of Native Modules and different architectural approaches to using them. This section was all about laying the groundwork for why and how to integrate native code in React Native.
3- Creating a Native iOS Widget Module: In the following part, we dove into iOS, creating a Native iOS Widget module. This included covering how to structure native iOS code and ensuring smooth communication between the Widget and React Native. We took a detailed, step-by-step approach to build a working widget that seamlessly interacts with the React Native app.
the link here: https://medium.com/@malikchohra/create-native-android-and-ios-module-in-react-native-use-widget-in-native-ios-24e227050654
4- Setting Up for Android: We replicated this process for Android, tackling the specific details of Android’s native environment, and showing how to set up a module that integrates smoothly with React Native. This included exploring Android-specific configurations, architecture, and communication channels.
Final Component
In this final section, I’ll walk you through a component in React Native (using TypeScript) that communicates with both iOS and Android native modules. I’ve also shared the GitHub project, so you can access the complete code, try it out, and see how everything fits together.
This tutorial is packed with information to give you a deep understanding of Native Modules in React Native. By the end, you’ll be well-equipped to create native components that interact with React Native — an invaluable skill for building cross-platform apps with native capabilities
the app interface to use the useRandomPhrase hook
export const MotivationalWidget: React.FC = () => {
const {currentPhrase, selectRandomPhrase} = useRandomPhrase();
return (
<View style={styles.container}>
<Text style={styles.phrase}>{currentPhrase}</Text>
<TouchableOpacity style={styles.button} onPress={selectRandomPhrase}>
<Text style={styles.buttonText}>New Motivation</Text>
</TouchableOpacity>
{(Platform.OS === 'android' || Platform.OS === 'ios') && (
<Text style={styles.widgetInfo}>
This phrase is also displayed in the home screen widget!
</Text>
)}
</View>
);
};
Screenshots of how it works:
Github Open Source Project for Native Android and IOS in React native:
Don’t forget to add a star to the project, if you have any questions, don’t hesitate to ping me
Conclusion
Through these three articles, we’ve taken a comprehensive journey into the world of Native Modules in React Native. We began with an overview of how React Native operates and laid the groundwork with a simple feature, which helped us understand the flow of a React Native app. From there, we moved into the core concept of Native Modules, exploring architectural options and learning how native code can extend the capabilities of a React Native app.
Our deep dive into creating native modules for both iOS and Android allowed us to experience the unique aspects of each platform. Building a Native iOS Widget module taught us about iOS-specific architecture and communication pathways, while the Android module development helped us understand the Android environment and configurations needed for seamless integration.
By putting it all together in a component that communicates with native code on both platforms, you now have a hands-on understanding of how to bridge the gap between React Native and native functionality. The skills you’ve built here are essential for any mid-level or advanced React Native developer aiming to enhance app capabilities and deliver a more robust user experience.
Armed with this knowledge, you’re ready to take on more complex native integrations, giving you a significant advantage in the cross-platform development space. Thank you for following along, and happy coding!
If you need to integrate Advanced functionalities in your Mobile app, create one from scratch, or need consulting in react native. Visit the casainnov.com, and check their mobile app page, and contact them there.
Bachelor's degree at Vietnam National University, Hanoi
3 个月do you have sample native module c++
React Native Mobile Engineer | Sharing Insights on Software Engineering | Digital Nomad Lifestyle | Passionate about Team Collaboration, processes and modern technology
3 个月Github Link: https://github.com/chohra-med/rnWidget