iosxcodereact-nativereact-native-turbomodule

How to set multiple specs for iOS turbomodule?


I take a reference from official document, it's working. https://reactnative.dev/docs/turbo-native-modules-introduction?platforms=ios

But when I try to set multiple specs for iOS, I get the error

10 duplicate symbols
Linker command failed with exit code 1 (use -v to see invocation)

Here is what I try:

step 1 npx @react-native-community/cli@latest init FirstApp

"react-native": "0.77.0",

step 2 add codegen setting in package.json

  "codegenConfig": {
    "libraries": [
      {
        "name": "NativeLocalStorageSpec",
        "type": "modules",
        "jsSrcsDir": "specs",
        "android": {
          "javaPackageName": "com.nativelocalstorage"
        }
      },
      {
        "name": "NativeBluetoothSpec",
        "type": "modules",
        "jsSrcsDir": "specs",
        "android": {
          "javaPackageName": "com.nativebluetooth"
        }
      }
    ]
  },

step 3 add file under specs folder (RN root project)

specs/NativeBluetooth.ts

import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  enableBluetooth(): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('NativeBluetooth');

specs/NativeLocalStorage.ts

import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';

export interface Spec extends TurboModule {
  setItem(value: string, key: string): void;
  getItem(key: string): string | null;
  removeItem(key: string): void;
  clear(): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('NativeLocalStorage');

step 4 type terminal command

cd ios

bundle install

bundle exec pod install

and then open XCode build the project get the error:

10 duplicate symbols

Linker command failed with exit code 1 (use -v to see invocation)

What is the problem ?


Solution

  • The implementation has changed I believe. Here is the documentation

    Here is how I have done it:

      "codegenConfig": {
        "name": "MyAppSpec",
        "type": "all",
        "jsSrcsDir": "specs",
        "android": {
          "javaPackageName": "com.myApp"
        }
      },
    

    Then in the specs folder:

    // NativeIconchooser.ts
    import { TurboModule, TurboModuleRegistry } from 'react-native';
    
    export interface Spec extends TurboModule {
      changeIcon: (iconName: string) => Promise<string>;
      getAvailableIcons: () => Promise<string[]>;
      getIcon: () => string;
    }
    
    export default TurboModuleRegistry.getEnforcing<Spec>('NativeIconChooser');
    
    // NativeSentiveInfo.ts
    import { TurboModule, TurboModuleRegistry } from 'react-native';
    
    export interface RNSensitiveInfoOptions {
      keychainService?: string;
      sharedPreferencesName?: string;
    }
    
    export interface Spec extends TurboModule {
      setItem: (key: string, value: string, options: RNSensitiveInfoOptions) => Promise<null>;
      getItem: (key: string, options: RNSensitiveInfoOptions) => Promise<string>;
      deleteItem: (key: string, options: RNSensitiveInfoOptions) => Promise<null>;
    }
    
    export default TurboModuleRegistry.getEnforcing<Spec>('NativeSensitiveInfo');
    

    In objective-c:

    // RCTNativeIconChooser.h
    #import <UIKit/UIKit.h>
    #import "MyAppSpec/MyAppSpec.h"
    
    @interface RCTNativeIconChooser : NSObject <NativeIconChooserSpec>
    
    @end
    
    // RCTNativeIconChooser.mm
    #import "RCTNativeIconChooser.h"
    
    
    @implementation RCTNativeIconChooser
    RCT_EXPORT_MODULE(NativeIconChooser)
    
    + (BOOL)requiresMainQueueSetup {
      return NO;
    }
    
    - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
      return std::make_shared<facebook::react::NativeIconChooserSpecJSI>(params);
    }
    
    - (NSString *)getIcon {
      NSString *currentIcon = [[UIApplication sharedApplication] alternateIconName];
      if (currentIcon) {
        return currentIcon;
      }
      
      return @"default";
    }
    // ...
    
    @end
    
    // RCTNativeSensitiveInfo.h
    #import <Foundation/Foundation.h>
    #import <Security/Security.h>
    #import "MyAppSpec/MyAppSpec.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface RCTNativeSensitiveInfo : NSObject <NativeSensitiveInfoSpec>
    
    @end
    
    NS_ASSUME_NONNULL_END