Skip to content

Commit

Permalink
Merge pull request #235 from OP-Engineering/expo-updates-workaround
Browse files Browse the repository at this point in the history
Add new expoUpdatesWorkaround function to avoid expo-updates crashing
  • Loading branch information
ospfranco authored Jan 26, 2025
2 parents 9c609a0 + 56c66b3 commit a0c6f27
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 138 deletions.
9 changes: 9 additions & 0 deletions cpp/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,13 @@ void install(jsi::Runtime &rt,
rt.global().setProperty(rt, "__OPSQLiteProxy", std::move(module));
}

void expoUpdatesWorkaround(const char *base_path) {
#ifdef OP_SQLITE_USE_LIBSQL
std::string path = std::string(base_path);
// Open a DB before anything else so that expo-updates does not mess up the
// configuration
opsqlite_libsql_open("__dummy", path, "");
#endif
}

} // namespace opsqlite
1 change: 1 addition & 0 deletions cpp/bindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ void install(jsi::Runtime &rt,
const char *base_path, const char *crsqlite_path,
const char *sqlite_vec_path);
void invalidate();
void expoUpdatesWorkaround(const char *base_path);

} // namespace opsqlite
21 changes: 21 additions & 0 deletions docs/docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ Some of the known offenders are:
- `cozodb`
- Any other package that might depend on sqlite

## Expo Updates

`expo-updates` now has a added a new way to avoid a hard dependency on sqlite. Adding `"expo.updates.useThirdPartySQLitePod": "true"` to `ios/Podfile.properties.json` fixes the duplicate symbols and header definition issues when `expo-updates` is the only conflicting package.

An expo plugin can also be used:
Expand Down Expand Up @@ -109,6 +111,25 @@ Another workaround for `expo-updates` and `expo-sqlite` you can use the iOS embe

This means however, you will be used whatever version the phone is running, which might be outdated and it also does not support extension loading. There is no way around this.

## Libsql

If you want to use expo-updates and libsql at the same time there is one more workaround you need to apply. On your `AppDelegate` (or wherever you initialize your RN view if it's a brownfield integration), you need to call `[OPSQLite expoUpdatesWorkaround];` before initializing the RN view. In case of a normal expo app modify the `AppDelegate.mm` as follows:

```objective-c
#import "OPSQLite.h" // Add the header

@implementation AppDelegate

-(BOOL)application: (UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions {
self moduleName = @"main";
self.initialProps = 0{};
[OPSQLite expoUpdatesWorkaround]; // Add the call to the workaround
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
```
# Other
For other conflicts and compilation errors there is no easy solution (Is there a solution?). You need to get rid of the double compilation by hand, either by patching the compilation of each package so that it still builds or removing the dependency on the package.
On Android you might be able to get away by just using a `pickFirst` strategy (here is an [article](https://ospfranco.com/how-to-resolve-duplicated-libraries-on-android/) on how to do that). On iOS depending on the build system you might be able to patch it via a post-build hook, something like:
Expand Down
45 changes: 24 additions & 21 deletions example/ios/OPSQLiteExample/AppDelegate.mm
Original file line number Diff line number Diff line change
@@ -1,42 +1,45 @@
#import "AppDelegate.h"

#import "OPSQLite.h"
#import <React/RCTBundleURLProvider.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.moduleName = @"OPSQLiteExample";
return [super application:application didFinishLaunchingWithOptions:launchOptions];
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.moduleName = @"OPSQLiteExample";
[OPSQLite expoUpdatesWorkaround];
return [super application:application
didFinishLaunchingWithOptions:launchOptions];
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
return [self bundleURL];
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
return [self bundleURL];
}

- (NSURL *)bundleURL
{
- (NSURL *)bundleURL {
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
return [[RCTBundleURLProvider sharedSettings]
jsBundleURLForBundleRoot:@"index"];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
return [[NSBundle mainBundle] URLForResource:@"main"
withExtension:@"jsbundle"];
#endif
}

/// This method controls whether the `concurrentRoot`feature of React18 is turned on or off.
/// This method controls whether the `concurrentRoot`feature of React18 is
/// turned on or off.
///
/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html
/// @note: This requires to be rendering on Fabric (i.e. on the New Architecture).
/// @return: `true` if the `concurrentRoot` feature is enabled. Otherwise, it returns `false`.
- (BOOL)concurrentRootEnabled
{
return true;
/// @note: This requires to be rendering on Fabric (i.e. on the New
/// Architecture).
/// @return: `true` if the `concurrentRoot` feature is enabled. Otherwise, it
/// returns `false`.
- (BOOL)concurrentRootEnabled {
return true;
}

-(BOOL)bridgelessEnabled
{
return YES;
- (BOOL)bridgelessEnabled {
return YES;
}

@end
13 changes: 2 additions & 11 deletions ios/OPSQLite.h
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
// #ifdef RCT_NEW_ARCH_ENABLED
// #import <OPSQLiteSpec/OPSQLiteSpec.h>
// #else
#import <React/RCTBridge.h>
// #endif

@interface OPSQLite : NSObject
// #ifdef RCT_NEW_ARCH_ENABLED
// <NativeOPSQLiteSpec>
// #else
<RCTBridgeModule>
// #endif
@interface OPSQLite : NSObject <RCTBridgeModule>

@property(nonatomic, assign) BOOL setBridgeOnMainQueue;

+ (void)expoUpdatesWorkaround;
@end
214 changes: 108 additions & 106 deletions ios/OPSQLite.mm
Original file line number Diff line number Diff line change
Expand Up @@ -13,143 +13,145 @@ @implementation OPSQLite
RCT_EXPORT_MODULE()

+ (BOOL)requiresMainQueueSetup {
return YES;
return YES;
}

- (NSDictionary *)constantsToExport {
NSArray *libraryPaths = NSSearchPathForDirectoriesInDomains(
NSLibraryDirectory, NSUserDomainMask, true);
NSString *libraryPath = [libraryPaths objectAtIndex:0];

NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, true);
NSString *documentPath = [documentPaths objectAtIndex:0];
return
@{@"IOS_DOCUMENT_PATH" : documentPath, @"IOS_LIBRARY_PATH" : libraryPath};
NSArray *libraryPaths = NSSearchPathForDirectoriesInDomains(
NSLibraryDirectory, NSUserDomainMask, true);
NSString *libraryPath = [libraryPaths objectAtIndex:0];

NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, true);
NSString *documentPath = [documentPaths objectAtIndex:0];
return @{
@"IOS_DOCUMENT_PATH" : documentPath,
@"IOS_LIBRARY_PATH" : libraryPath
};
}

- (NSDictionary *)getConstants {
return [self constantsToExport];
return [self constantsToExport];
}

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install) {
RCTCxxBridge *cxxBridge = (RCTCxxBridge *)_bridge;
if (cxxBridge == nil) {
return @false;
}

auto jsiRuntime = (facebook::jsi::Runtime *)cxxBridge.runtime;
if (jsiRuntime == nil) {
return @false;
}

auto &runtime = *jsiRuntime;
auto callInvoker = _bridge.jsCallInvoker;

// Get appGroupID value from Info.plist using key "AppGroup"
NSString *appGroupID =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"OPSQLite_AppGroup"];
NSString *documentPath;

if (appGroupID != nil) {
// Get the app groups container storage url
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *storeUrl = [fileManager
containerURLForSecurityApplicationGroupIdentifier:appGroupID];

if (storeUrl == nil) {
NSLog(@"OP-SQLite: Invalid AppGroup ID provided (%@). Check the value of "
@"\"AppGroup\" in your Info.plist file",
appGroupID);
return @false;
RCTCxxBridge *cxxBridge = (RCTCxxBridge *)_bridge;
if (cxxBridge == nil) {
return @false;
}

documentPath = [storeUrl path];
} else {
NSArray *paths = NSSearchPathForDirectoriesInDomains(
NSLibraryDirectory, NSUserDomainMask, true);
documentPath = [paths objectAtIndex:0];
}

NSBundle *crsqlite_bundle =
[NSBundle bundleWithIdentifier:@"io.vlcn.crsqlite"];
NSString *crsqlite_path = [crsqlite_bundle pathForResource:@"crsqlite"
ofType:@""];
NSBundle *libsqlitevec_bundle =
[NSBundle bundleWithIdentifier:@"com.ospfranco.sqlitevec"];
NSString *sqlite_vec_path = [libsqlitevec_bundle pathForResource:@"sqlitevec"
ofType:@""];

if (crsqlite_path == nil) {
crsqlite_path = @"";
}

if (sqlite_vec_path == nil) {
sqlite_vec_path = @"";
}

opsqlite::install(runtime, callInvoker, [documentPath UTF8String],
[crsqlite_path UTF8String], [sqlite_vec_path UTF8String]);
return @true;
auto jsiRuntime = (facebook::jsi::Runtime *)cxxBridge.runtime;
if (jsiRuntime == nil) {
return @false;
}

auto &runtime = *jsiRuntime;
auto callInvoker = _bridge.jsCallInvoker;

// Get appGroupID value from Info.plist using key "AppGroup"
NSString *appGroupID =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"OPSQLite_AppGroup"];
NSString *documentPath;

if (appGroupID != nil) {
// Get the app groups container storage url
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *storeUrl = [fileManager
containerURLForSecurityApplicationGroupIdentifier:appGroupID];

if (storeUrl == nil) {
NSLog(@"OP-SQLite: Invalid AppGroup ID provided (%@). Check the "
@"value of "
@"\"AppGroup\" in your Info.plist file",
appGroupID);
return @false;
}

documentPath = [storeUrl path];
} else {
NSArray *paths = NSSearchPathForDirectoriesInDomains(
NSLibraryDirectory, NSUserDomainMask, true);
documentPath = [paths objectAtIndex:0];
}

NSBundle *crsqlite_bundle =
[NSBundle bundleWithIdentifier:@"io.vlcn.crsqlite"];
NSString *crsqlite_path = [crsqlite_bundle pathForResource:@"crsqlite"
ofType:@""];
NSBundle *libsqlitevec_bundle =
[NSBundle bundleWithIdentifier:@"com.ospfranco.sqlitevec"];
NSString *sqlite_vec_path =
[libsqlitevec_bundle pathForResource:@"sqlitevec" ofType:@""];

if (crsqlite_path == nil) {
crsqlite_path = @"";
}

if (sqlite_vec_path == nil) {
sqlite_vec_path = @"";
}

opsqlite::install(runtime, callInvoker, [documentPath UTF8String],
[crsqlite_path UTF8String], [sqlite_vec_path UTF8String]);
return @true;
}

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getDylibPath : (
NSString *)bundleId andResource : (NSString *)resourceName) {
NSBundle *bundle = [NSBundle bundleWithIdentifier:bundleId];
NSString *path = [bundle pathForResource:resourceName ofType:@""];
return path;
NSBundle *bundle = [NSBundle bundleWithIdentifier:bundleId];
NSString *path = [bundle pathForResource:resourceName ofType:@""];
return path;
}

RCT_EXPORT_METHOD(moveAssetsDatabase : (NSDictionary *)args resolve : (
RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) {
NSString *documentPath = [NSSearchPathForDirectoriesInDomains(
NSLibraryDirectory, NSUserDomainMask, true) objectAtIndex:0];
NSString *documentPath = [NSSearchPathForDirectoriesInDomains(
NSLibraryDirectory, NSUserDomainMask, true) objectAtIndex:0];

NSString *filename = args[@"filename"];
BOOL overwrite = args[@"overwrite"];

NSString *filename = args[@"filename"];
BOOL overwrite = args[@"overwrite"];
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:filename
ofType:nil];

NSString *sourcePath = [[NSBundle mainBundle] pathForResource:filename
ofType:nil];
NSString *destinationPath =
[documentPath stringByAppendingPathComponent:filename];

NSString *destinationPath =
[documentPath stringByAppendingPathComponent:filename];
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:destinationPath]) {
if (overwrite) {
[fileManager removeItemAtPath:destinationPath error:&error];
if (error) {
NSLog(@"Error: %@", error);
resolve(@false);
return;
}
} else {
resolve(@true);
return;
}
}

NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:destinationPath]) {
if (overwrite) {
[fileManager removeItemAtPath:destinationPath error:&error];
if (error) {
[fileManager copyItemAtPath:sourcePath toPath:destinationPath error:&error];
if (error) {
NSLog(@"Error: %@", error);
resolve(@false);
return;
}
} else {
resolve(@true);
return;
}
}

[fileManager copyItemAtPath:sourcePath toPath:destinationPath error:&error];
if (error) {
NSLog(@"Error: %@", error);
resolve(@false);
resolve(@true);
return;
}
resolve(@true);
return;
}

// #if RCT_NEW_ARCH_ENABLED
// - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
// (const facebook::react::ObjCTurboModule::InitParams &)params
// {
// return std::make_shared<facebook::react::NativeOPSQLiteSpecJSI>(params);
// }
// #endif

- (void)invalidate {
opsqlite::invalidate();
opsqlite::invalidate();
}

+ (void)expoUpdatesWorkaround {
NSArray *paths = NSSearchPathForDirectoriesInDomains(
NSLibraryDirectory, NSUserDomainMask, true);
NSString *documentPath = [paths objectAtIndex:0];
opsqlite::expoUpdatesWorkaround([documentPath UTF8String]);
}

@end

0 comments on commit a0c6f27

Please sign in to comment.