Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HybridApp] Add HybridApp turbomodule #57406

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Mobile-Expensify
25 changes: 25 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2395,6 +2395,27 @@ PODS:
- React-logger (= 0.76.3)
- React-perflogger (= 0.76.3)
- React-utils (= 0.76.3)
- ReactNativeHybridApp (0.0.0):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.01.01.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNAppleAuthentication (2.2.2):
- React-Core
- RNCClipboard (1.15.0):
Expand Down Expand Up @@ -2963,6 +2984,7 @@ DEPENDENCIES:
- React-utils (from `../node_modules/react-native/ReactCommon/react/utils`)
- ReactCodegen (from `build/generated/ios`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- "ReactNativeHybridApp (from `../node_modules/@expensify/react-native-hybrid-app`)"
- "RNAppleAuthentication (from `../node_modules/@invertase/react-native-apple-authentication`)"
- "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)"
- "RNCPicker (from `../node_modules/@react-native-picker/picker`)"
Expand Down Expand Up @@ -3233,6 +3255,8 @@ EXTERNAL SOURCES:
:path: build/generated/ios
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
ReactNativeHybridApp:
:path: "../node_modules/@expensify/react-native-hybrid-app"
RNAppleAuthentication:
:path: "../node_modules/@invertase/react-native-apple-authentication"
RNCClipboard:
Expand Down Expand Up @@ -3421,6 +3445,7 @@ SPEC CHECKSUMS:
React-utils: 2bcaf4f4dfe361344bce2fae428603d518488630
ReactCodegen: ae99a130606068ed40d1d9c0d5f25fda142a0647
ReactCommon: 89c87b343deacc8610b099ac764848f0ce937e3e
ReactNativeHybridApp: 18cab0d029f08d51391a07c5c2c7872287544485
RNAppleAuthentication: 0571c08da8c327ae2afc0261b48b4a515b0286a6
RNCClipboard: 8212ca9e8370d0e23bfb7f5a591380da5a63456d
RNCPicker: b978067931744f5a7316b48b8dcf145d4d722672
Expand Down
7 changes: 7 additions & 0 deletions jest/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ jest.mock('../modules/background-task/src/NativeReactNativeBackgroundTask', () =
onBackgroundTaskExecution: jest.fn(),
}));

jest.mock('../modules/hybrid-app/src/NativeReactNativeHybridApp', () => ({
isHybridApp: jest.fn(),
closeReactNativeApp: jest.fn(),
completeOnboarding: jest.fn(),
switchAccount: jest.fn(),
}));

// This makes FlatList render synchronously for easier testing.
jest.mock(
'@react-native/virtualized-lists/Interaction/Batchinator',
Expand Down
22 changes: 22 additions & 0 deletions modules/hybrid-app/ReactNativeHybridApp.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require "json"

package = JSON.parse(File.read(File.join(__dir__, "package.json")))

Pod::Spec.new do |s|
s.name = "ReactNativeHybridApp"
s.version = package["version"]
s.summary = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.authors = package["author"]

s.platforms = { :ios => min_ios_version_supported }
s.source = { :git => ".git", :tag => "#{s.version}" }

s.source_files = "ios/**/*.{h,m,mm,cpp}"
s.pod_target_xcconfig = {
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
}

install_modules_dependencies(s)
end
111 changes: 111 additions & 0 deletions modules/hybrid-app/android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
buildscript {
ext.getExtOrDefault = {name ->
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['ReactNativeHybridApp_' + name]
}

repositories {
google()
mavenCentral()
}

dependencies {
classpath "com.android.tools.build:gradle:8.7.2"
// noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
}
}


def isNewArchitectureEnabled() {
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
}

apply plugin: "com.android.library"
apply plugin: "kotlin-android"

if (isNewArchitectureEnabled()) {
apply plugin: "com.facebook.react"
}

def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ReactNativeHybridApp_" + name]).toInteger()
}

def supportsNamespace() {
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
def major = parsed[0].toInteger()
def minor = parsed[1].toInteger()

// Namespace support was added in 7.3.0
return (major == 7 && minor >= 3) || major >= 8
}

android {
if (supportsNamespace()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We know that we have higher version of gradle so we could remove it too.

namespace "com.expensify.reactnativehybridapp"

sourceSets {
main {
manifest.srcFile "src/main/AndroidManifestNew.xml"
}
}
}

compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")

defaultConfig {
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
}

buildFeatures {
buildConfig true
}

buildTypes {
release {
minifyEnabled false
}
}

lintOptions {
disable "GradleCompatible"
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

sourceSets {
main {
if (isNewArchitectureEnabled()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are always having new arch so we could remove it but w/e

java.srcDirs += [
"generated/java",
"generated/jni"
]
}
}
}
}

repositories {
mavenCentral()
google()
}

def kotlin_version = getExtOrDefault("kotlinVersion")

dependencies {
implementation "com.facebook.react:react-android"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

if (isNewArchitectureEnabled()) {
react {
jsRootDir = file("../src/")
libraryName = "ReactNativeHybridApp"
codegenJavaPackageName = "com.expensify.reactnativehybridapp"
}
}
Comment on lines +105 to +111
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (isNewArchitectureEnabled()) {
react {
jsRootDir = file("../src/")
libraryName = "ReactNativeHybridApp"
codegenJavaPackageName = "com.expensify.reactnativehybridapp"
}
}

These lines are not needed

5 changes: 5 additions & 0 deletions modules/hybrid-app/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ReactNativeHybridApp_kotlinVersion=2.0.21
ReactNativeHybridApp_minSdkVersion=24
ReactNativeHybridApp_targetSdkVersion=34
ReactNativeHybridApp_compileSdkVersion=35
ReactNativeHybridApp_ndkVersion=27.1.12297006
3 changes: 3 additions & 0 deletions modules/hybrid-app/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are those manifests needed?

package="com.expensify.reactnativehybridapp">
</manifest>
2 changes: 2 additions & 0 deletions modules/hybrid-app/android/src/main/AndroidManifestNew.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.expensify.reactnativehybridapp

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.annotations.ReactModule

@ReactModule(name = ReactNativeHybridAppModule.NAME)
class ReactNativeHybridAppModule(reactContext: ReactApplicationContext) :
NativeReactNativeHybridAppSpec(reactContext) {

override fun getName(): String {
return NAME
}

override fun isHybridApp(): Boolean {
return false
}

override fun closeReactNativeApp(shouldSignOut: Boolean, shouldSetNVP: Boolean) {}

override fun completeOnboarding(status: Boolean) {}

override fun switchAccount(
newDotCurrentAccountEmail: String?,
authToken: String?,
policyID: String?,
accountID: String?
) {}

companion object {
const val NAME = "ReactNativeHybridAppModule"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.expensify.reactnativehybridapp

import com.facebook.react.BaseReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider
import java.util.HashMap

class ReactNativeHybridAppPackage : BaseReactPackage() {
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
return if (name == ReactNativeHybridAppModule.NAME) {
ReactNativeHybridAppModule(reactContext)
} else {
null
}
}

override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
return ReactModuleInfoProvider {
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
moduleInfos[ReactNativeHybridAppModule.NAME] = ReactModuleInfo(
ReactNativeHybridAppModule.NAME,
ReactNativeHybridAppModule.NAME,
false, // canOverrideExistingModule
false, // needsEagerInit
false, // isCxxModule
true // isTurboModule
)
moduleInfos
}
}
}
6 changes: 6 additions & 0 deletions modules/hybrid-app/ios/ReactNativeHybridApp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

#import "RNReactNativeHybridAppSpec/RNReactNativeHybridAppSpec.h"

@interface ReactNativeHybridApp : NSObject <NativeReactNativeHybridAppSpec>

@end
22 changes: 22 additions & 0 deletions modules/hybrid-app/ios/ReactNativeHybridApp.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#import "ReactNativeHybridApp.h"

@implementation ReactNativeHybridApp
RCT_EXPORT_MODULE()

- (NSNumber *)isHybridApp {
return @false;
}

- (void)closeReactNativeApp:(BOOL)shouldSignOut shouldSetNVP:(BOOL)shouldSetNVP {}

- (void)completeOnboarding:(BOOL)status {}

- (void)switchAccount:(NSString *)newDotCurrentAccountEmail authToken:(NSString *)authToken policyID:(NSString *)policyID accountID:(NSString *)accountID {}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeReactNativeHybridAppSpecJSI>(params);
}

@end
19 changes: 19 additions & 0 deletions modules/hybrid-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@expensify/react-native-hybrid-app",
"version": "0.0.0",
"description": "HybridApp",
"main": "src/index",
"codegenConfig": {
"name": "RNReactNativeHybridAppSpec",
"type": "modules",
"jsSrcsDir": "src"
},
"author": " <> ()",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we want to fill those fields too?

"license": "UNLICENSED",
"homepage": "#readme",
"create-react-native-library": {
"type": "turbo-module",
"languages": "kotlin-objc",
"version": "0.48.1"
}
}
12 changes: 12 additions & 0 deletions modules/hybrid-app/react-native.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @type {import('@react-native-community/cli-types').UserDependencyConfig}
*/
module.exports = {
dependency: {
platforms: {
android: {
cmakeListsPath: 'build/generated/source/codegen/jni/CMakeLists.txt',
},
},
},
};
12 changes: 12 additions & 0 deletions modules/hybrid-app/src/NativeReactNativeHybridApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';

// eslint-disable-next-line rulesdir/no-inline-named-export, @typescript-eslint/consistent-type-definitions
export interface Spec extends TurboModule {
isHybridApp: () => boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would even store this value somewhere in order not to do a call to native code each time it is checked. It won't change during the lifetime of app so it should be safe I guess.

closeReactNativeApp: (shouldSignOut: boolean, shouldSetNVP: boolean) => void;
completeOnboarding: (status: boolean) => void;
switchAccount: (newDotCurrentAccountEmail: string, authToken: string, policyID: string, accountID: string) => void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('ReactNativeHybridApp');
5 changes: 5 additions & 0 deletions modules/hybrid-app/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import ReactNativeHybridApp from './NativeReactNativeHybridApp';

const HybridAppModule = ReactNativeHybridApp;

export default HybridAppModule;
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading