Create Native Android Module with Kotlin and Native Ios Module with Swift and use them in React Native

Create Native Android Module with Kotlin and Native Ios Module with Swift and use them in React Native


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:

  1. React Native Fundamentals: How React Native operates and its core concepts.
  2. Native Module Communication: The mechanisms for seamless interaction between native modules and React Native components.
  3. Native Module Development: Creating custom native modules for Android, iOS, and the React Native side.
  4. The New React Native Architecture: Exploring the latest architecture and its implementation benefits.

###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:

  • Over 104,000 stars on GitHub (as of early 2024)
  • Thousands of open-source packages and components
  • Regular contributions from major companies like Microsoft, Shopify, and Meta
  • Extensive documentation and learning resources
  • Active discussion forums and community support

Despite the rich ecosystem, there are scenarios where the existing JavaScript APIs and community packages don’t suffice. Common situations include:

  1. Accessing platform-specific APIs not exposed by React Native
  2. Optimizing performance-critical operations
  3. Integrating with native SDKs
  4. Implementing complex platform-specific features


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:

  1. Android: Write a Java/Kotlin class that extends ReactContextBaseJavaModule, implementing the functions you want to expose to JavaScript.
  2. iOS: Create a Swift/Objective-C class that conforms to the RCTBridgeModule protocol, with methods that can be called from JavaScript.

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.

  1. For instance, in Android, you can use reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit() to send an event from native code to JavaScript.
  2. In iOS, you would use sendEvent(withName: body:) from the RCTEventEmitter class.

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:

  • When a user interacts with a UI component in React Native, JavaScript can call a native method to perform an action on the native side.
  • Similarly, if something happens in the native environment (e.g., a network status change), the native code can send an event to the JavaScript layer to notify it about the change, allowing the app to react appropriately.

This communication is facilitated by event emitters on both platforms:

  • Android: Use DeviceEventManagerModule.RCTDeviceEventEmitter to send events to JavaScript.
  • iOS: Use RCTEventEmitter to emit events that JavaScript can listen to.

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:

  • You use NativeModules to access the native module from JavaScript. This allows you to call native functions as if they were JavaScript functions.

and React Native can handle it automatically.

Our App will have the following Diagram:

To update a widget from a React Native app:

  • JavaScript Code: Calls WidgetModule.updateWidget(text) with the desired text.
  • Native Code on Android (Kotlin): The updateWidget method updates the widget's text.
  • Native Code on iOS (Swift): The updateWidget function updates the iOS widget similarly.

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

  • Minimize data transfer: Avoid passing large amounts of data between JavaScript and native code. Use efficient data structures and consider serialization techniques.
  • Optimize native code: Write efficient native code using appropriate algorithms and data structures. Profile your code to identify bottlenecks and optimize accordingly.
  • Asynchronous operations: Use asynchronous operations for tasks that might block the main thread, such as network requests or complex calculations.
  • Avoid unnecessary bridging: Minimize the number of times you cross the JavaScript-native bridge. Batch operations or use asynchronous callbacks to reduce overhead.

Memory Management

  • Release resources: Ensure that native resources (e.g., memory, file handles) are properly released when they are no longer needed. Use appropriate memory management techniques in your native code.
  • Avoid memory leaks: Be cautious of memory leaks, especially in long-running applications. Use tools like the Android Memory Profiler or Xcode Instruments to detect and fix memory leaks.

Error Handling Patterns

  • Propagate errors: Handle errors gracefully in your native modules and propagate them back to JavaScript using promises or callbacks.
  • Provide informative error messages: Include meaningful error messages to help developers diagnose and fix issues.
  • Use try-catch blocks: Surround potentially error-prone code with try-catch blocks to catch exceptions and handle them appropriately.

Documentation Standards

  • Clear and concise documentation: Provide clear and concise documentation for your native modules, including usage instructions, parameters, return values, and potential errors.
  • Use JSDoc: Use JSDoc to document your native modules in JavaScript, making it easier for developers to understand and use them.

Testing Native Modules

  • Unit testing: Write unit tests for your native modules to ensure that they function correctly and handle various input scenarios.
  • Integration testing: Test the interaction between your native modules and the React Native app to verify that they work together as expected.
  • End-to-end testing: Use end-to-end testing to validate the overall behavior of your app, including the functionality provided by your 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:

  • View Controllers manage screens and user interactions in iOS apps. They determine what appears on the screen and handle user inputs like taps and gestures.
  • Example: In React Native, this is similar to how you use components to manage different screens with libraries like React Navigation.

2. Views:

  • Views are the basic building blocks of the UI in iOS, like buttons, labels, and images. They define the elements that users see and interact with.
  • Example: In React Native, views are equivalent to components like <View>, <Text>, and <Button>.

3. App Delegates:

  • The App Delegate manages the application’s lifecycle, such as when it starts, goes to the background, or shuts down.

4. Extensions:

  • Extensions in iOS are used to add features to an app, like widgets or sharing capabilities.

5. Frameworks:

  • Frameworks are libraries of reusable code that provide specific functionality. UIKit is the framework used for building user interfaces.
  • Example: Think of frameworks in iOS like packages or libraries in JavaScript. Just as you use libraries like Axios for HTTP requests, UIKit provides the building blocks for UI in iOS.

6. UIKit

  • What It Is: UIKit is the primary framework for building user interfaces on iOS.
  • Usage: It provides components like UIView, UIButton, UILabel, and many more, which are used to create the UI.
  • React Native Equivalent: The <View>, <Text>, and <TouchableOpacity> components in React Native are similar to UIKit's views.
  • Example: If you want to create a button in Swift using UIKit, you would do:

let button = UIButton(type: .system)
 button.setTitle("Click me", for: .normal)        

7. AppKit

  • What It Is: AppKit is used for building macOS applications. While similar to UIKit, it has components suited for desktop interfaces.
  • Usage in Widgets: If you’re building macOS widgets or applications, you would use AppKit instead of UIKit.

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.

  • Shared Container: An App Group creates a shared container where apps can store and access data.
  • Group Identifier: Each App Group has a unique identifier that is used to access the shared container.
  • Data Sharing: Apps within the same App Group can read and write data to the shared container.
  • Security: Data shared within an App Group is protected by iOS’s security mechanisms.

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:

  • Navigate to your iOS folder in your React Native project and open the .xcworkspace file in Xcode.

2. Add a Widget Target:

  • In Xcode, go to File > New > Target.
  • Select Widget Extension from the list and click Next.
  • Name your widget (e.g., “MotivationalWidget”) and ensure it’s added to your app’s group.

3. Configure App Groups:

  • Go to your app’s target settings in Xcode.
  • Under the Signing & Capabilities tab, add a new capability for App Groups.
  • Create a new app group (e.g., group.org.reactjs.native.example.aiWidgetRN.WidgetMotivation) and ensure both your app and widget extension are part of this group.

Step 2: Implement the Widget Logic

1. Edit the Widget Code:

  • Navigate to the newly created widget extension folder in Xcode.
  • Open the WidgetMotivation.swift file (or similar) and implement the widget’s UI and logic. Use SwiftUI to design the widget interface.

2. Access Shared Data:

  • Use UserDefaults with the app group to share data between your app and the widget.

3. Update Widget Content:

  • Implement a method to update the widget’s content. This can be triggered from your React Native code using the updateWidget method you have in WidgetModuleApp.m.

Step 3: Connect React Native with the Widget

1. Bridge React Native and Native Code:

  • Ensure your React Native module (WidgetModuleApp.m) is correctly set up to communicate with the native iOS code.
  • Use the RCT_EXPORT_METHOD to expose methods to JavaScript, as you’ve done with updateWidget.

2. Trigger Widget Updates:

  • From your React Native app, call the updateWidget method to update the widget’s content.
  • This method should update the UserDefaults and trigger a widget reload using the WidgetController.reloadWidgetContent method.

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:

  • Build and run your app in Xcode to ensure the widget is correctly integrated.
  • Test the widget on a simulator or a physical device to verify its functionality.

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:

Widget app 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:

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

  • Broadcast Receivers:

Listen for system-wide events or custom app broadcasts, such as connectivity changes

  • Content Providers:

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 Overview: Kotlin is the preferred language for modern Android development, thanks to its concise syntax, null safety, and functional programming support. However, Java remains relevant, as much of the Android framework is written in Java.

Kotlin, different concepts make it favorable nowadays in Native Android development:

  • Data classes: Automatically generate getters, setters, equals(), hashCode(), toString(), and copy() methods for simple data structures.
  • Extension functions: Extend existing classes with new functionality without modifying the original class.
  • Coroutines: Simplify asynchronous programming and make it more readable

Displaying Interfaces (UI Handling)

Android uses activities and fragments to represent screens or parts of a screen.

Activities:

  • The main building blocks of Android apps. Each activity represents a single screen.
  • In React Native, similar to components used with navigation libraries to handle different app screens.

Fragments:

  • Reusable components that can be added to activities to create more complex layouts.
  • In React Native, modular components serve a similar purpose for creating complex layouts.

Handling Functions and Events

In Android, functions are typically methods defined within classes. Events are handled using listeners or callbacks.

  • Methods: Define methods using the fun keyword in Kotlin or public void in Java.
  • Event listeners: Implement interfaces like OnClickListener or OnTouchListener to handle specific events.

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 intents: Used to start specific activities within your app.
  • Implicit intents: Used to start activities in other apps based on their capabilities

// 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:

  • Model: Represents data.
  • View: Defines the UI.
  • ViewModel: Acts as a mediator between the Model and View.

Benefits of MVVM:

  • Testability: Easier to test the ViewModel independently.
  • Maintainability: Clear separation of concerns.
  • Reusability: Reusable ViewModels

Let’s Start the Widget Native Android Implementation

Step 1: Create the widget on Android

  • Create a new Java or Kotlin class that extends AppWidgetProvider.
  • This class will handle updates and interactions with the widget.

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

  • Add a <receiver> entry for your widget provider in the AndroidManifest.xml.
  • Define the widget’s metadata using an XML file in res/xml (e.g., widget_info.xml).

//.....
// 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

  1. Bridge React Native and Native Code:

  • Ensure your React Native module (WidgetModule.kt) is correctly set up to communicate with the native Android code.
  • Use the @ReactMethod annotation to expose methods to JavaScript, as you’ve done with updateWidget.

2. Trigger Widget Updates:

  • From your React Native app, call the updateWidget method to update the widget’s content.
  • This method should update the SharedPreferences and trigger a widget update using an Intent.

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:

android widget app


###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.

The link Here: https://medium.com/@malikchohra/build-for-scale-create-native-android-and-ios-module-in-react-native-prepare-react-native-app-09d615ba2450

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.

the link here: https://medium.com/@malikchohra/build-for-scale-create-native-android-and-ios-module-in-react-native-prepare-react-native-app-09d615ba2450

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.

the link: https://medium.com/@malikchohra/create-native-android-and-ios-module-in-react-native-use-widget-in-native-android-6e2f4a09d263

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:

https://github.com/chohra-med/rnWidget

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.

I share insights about React Native, React, and Typescript. Follow me on Linkedin or Medium.

??ng Hoàng

Bachelor's degree at Vietnam National University, Hanoi

3 个月

do you have sample native module c++

Malik Chohra

React Native Mobile Engineer | Sharing Insights on Software Engineering | Digital Nomad Lifestyle | Passionate about Team Collaboration, processes and modern technology

3 个月

要查看或添加评论,请登录

Malik Chohra的更多文章

社区洞察

其他会员也浏览了