Skip to content

@import ReactNativeHealthkit from an external Objective-C++ file fails: generated -Swift.h references margelo::nitro types as if NitroModules was never imported #361

Description

@yousifabroaf-cpu

Summary

BackgroundDeliveryManager (and presumably other native APIs meant to be called directly from AppDelegate/native code, per the README's "Usage from AppDelegate.swift" section) cannot actually be used from outside the ReactNativeHealthkit CocoaPods target. Importing the module from a plain Objective-C++ (.mm) file — the standard way to consume a Swift module from native code without forcing a target-wide Swift/C++ interop mode change — fails because the library's auto-generated Swift-to-C++ compatibility header (ReactNativeHealthkit-Swift.h) references multiple margelo::nitro::* types without itself importing NitroModules, and an explicit @import NitroModules; placed before @import ReactNativeHealthkit; in the consuming file does not fix it.

This makes the documented BackgroundDeliveryManager.shared.setupBackgroundObservers() AppDelegate integration effectively unusable from any file outside the library's own pod target.

Environment

  • @kingstinct/react-native-healthkit: 14.0.2
  • react-native-nitro-modules: 0.35.9
  • react-native: 0.85.3
  • expo: 56.0.12
  • Xcode: 26.5 (Build 17F42)
  • Swift: 6.3.2 (swiftlang-6.3.2.1.108 clang-2100.1.1.101)
  • iOS SDK: iphonesimulator26.5
  • CocoaPods, default use_frameworks! off (static libraries)

Minimal reproduction

A two-file Objective-C++ bridge added to the main app target (not the library's own pod target):

HealthKitBackgroundDeliveryBridge.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface HealthKitBackgroundDeliveryBridge : NSObject
+ (void)setupBackgroundObservers;
@end

NS_ASSUME_NONNULL_END

HealthKitBackgroundDeliveryBridge.mm

#import "HealthKitBackgroundDeliveryBridge.h"

@import NitroModules;
@import ReactNativeHealthkit;

@implementation HealthKitBackgroundDeliveryBridge

+ (void)setupBackgroundObservers {
  [[BackgroundDeliveryManager shared] setupBackgroundObservers];
}

@end

Build settings applied to the consuming app target only (confirmed necessary, not the issue):

  • CLANG_ENABLE_MODULES = YES
  • OTHER_CPLUSPLUSFLAGS = $(inherited) -fcxx-modules
  • HEADER_SEARCH_PATHS includes $(PODS_ROOT)/Headers/Private/NitroModules

Actual error

Why this looks like a module-dependency declaration issue, not a consumer-side config problem

chrono_time (NitroModules/DateToChronoDate.hpp) and SharedAnyMap (NitroModules/AnyMapUtils.hpp) are both simple, unconditional using type aliases in the margelo::nitro namespace:

// AnyMapUtils.hpp
#include "AnyMap.hpp"
namespace margelo::nitro {
  using SharedAnyMap = std::shared_ptr<AnyMap>;
  ...
}

// DateToChronoDate.hpp
#include "NitroTypeInfo.hpp"
#include <chrono>
namespace margelo::nitro {
  using chrono_time = std::chrono::system_clock::time_point;
  ...
}

Both headers correctly #include their own dependencies and are both listed in NitroModules-umbrella.h. Ruled out before filing this:

  • Not a stale build/cache issue — reproduces after a full DerivedData wipe and a clean pod install.
  • Not a missing header search pathPods/Headers/Private/NitroModules (where the sibling headers these files include live) is explicitly added; a different, earlier "file not found" error for JSIConverter+AnyMap.hpp was fixed by adding that path, and disappeared. This is a different, later error.
  • Not a missing @import@import NitroModules; is present, and ordered before @import ReactNativeHealthkit; specifically to rule out an ordering issue.
  • Not file-extension/language-mode related.mm files are unambiguously Objective-C++ regardless of any target-wide Swift interop setting, and -fcxx-modules/CLANG_ENABLE_MODULES are both set.

Two different, unrelated symbols from two different headers fail identically despite a structurally correct import. The most plausible explanation: Clang modules are built and cached independently per-module. ReactNativeHealthkit's own Clang module is apparently compiled in a way that doesn't itself resolve its dependency on NitroModules's types when that compilation is triggered from a context outside ReactNativeHealthkit's own CocoaPods target — so an @import NitroModules; in the consuming file doesn't retroactively make those types visible inside ReactNativeHealthkit's own module build. This would point to ReactNativeHealthkit's module map (or whatever generates/wraps ReactNativeHealthkit-Swift.h) not declaring NitroModules as a dependency in a way that survives external consumption — possibly fine when the module is only ever consumed from within its own pod target (where build order/precompiled context happens to make the types available incidentally) but breaking for the documented AppDelegate-integration use case, which is inherently external.

Impact

The documented background-delivery AppDelegate integration (BackgroundDeliveryManager.shared.setupBackgroundObservers()) cannot currently be wired up at all from a project using static libraries (no use_frameworks!) without hitting this — whether called directly from Swift (which has its own, separate problems with target-wide interop mode breaking React Native's own headers) or from an Objective-C++ bridge (this issue). Happy to provide a full minimal reproduction repo if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions