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

Native side not loading with custom AppDelegate.swift #4631

Closed
danielRChildrens opened this issue Jun 26, 2023 · 15 comments · Fixed by #4880
Closed

Native side not loading with custom AppDelegate.swift #4631

danielRChildrens opened this issue Jun 26, 2023 · 15 comments · Fixed by #4880
Labels
Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snippet of code, snack or repo is provided

Comments

@danielRChildrens
Copy link

danielRChildrens commented Jun 26, 2023

Description

I am receiving the following error after converting the AppDelegate.mm to AppDelegate.swift:

Error: [Reanimated] The native part of Reanimated doesn't seem to be initialized.

AppDelegate.swift

import Foundation

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  var window: UIWindow?
  var bridge: RCTBridge!

  func application(_ application:UIApplication, didFinishLaunchingWithOptions launchOptions:[UIApplication.LaunchOptionsKey : Any]?) -> Bool {
    let jsCodeLocation: URL
    
    jsCodeLocation = RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")

    let rootView = RCTRootView(bundleURL: jsCodeLocation, moduleName: "DigitalFrontDoor", initialProperties: nil, launchOptions: launchOptions)

    let rootViewController = UIViewController()
    rootViewController.view = rootView
    self.window = UIWindow(frame: UIScreen.main.bounds)
    self.window?.rootViewController = rootViewController
    self.window?.makeKeyAndVisible()
    
    return true
    
  }


  /// 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`.
  func concurrentRootEnabled() -> Bool {
    return true
  }
}

Notes:

  • The iOS build is completing successfully
  • Android is working as expected
  • I have tried removing the Pods, node_modules, build folders and reinstalling everything.
  • I have tried restarting my computer
  • Converting the AppDelegate to swift works without Reanimated
  • I have followed this post for the AppDelegate swift migration (https://ospfranco.com/post/2021/07/31/migrate-rn-appdelegate-to-swift/)

Steps to reproduce

I have two Branches as follows:

  • Branch 1 (reanimated): Regular AppDelegate with Reanimated, In this branch everything works as expected in both platforms
  • Branch 2 (swift-app-delegate): Swift AppDelegate without Reanimated, In this branch everything works as expected in both platforms

When both branches are merged (swift-appdelegate-reanimated), getting Swift AppDelegate With Reanimated, the iOS build works, but when the app is launched I get the error:

image

Snack or a link to a repository

https://github.com/danielRChildrens/SwiftReanimated/tree/swift-appdelegate-reanimated

Reanimated version

3.3.0

React Native version

0.71.6

Platforms

iOS

JavaScript runtime

Hermes

Workflow

React Native (without Expo)

Architecture

Paper (Old Architecture)

Build type

Debug mode

Device

iOS simulator

Device model

iOS 16 iphone 14

Acknowledgements

Yes

@danielRChildrens danielRChildrens added the Needs review Issue is ready to be reviewed by a maintainer label Jun 26, 2023
@github-actions github-actions bot added Platform: iOS This issue is specific to iOS Missing info The user didn't precise the problem enough Missing repro This issue need minimum repro scenario Repro provided A reproduction with a snippet of code, snack or repo is provided and removed Missing repro This issue need minimum repro scenario Missing info The user didn't precise the problem enough labels Jun 26, 2023
@tomekzaw tomekzaw removed the Needs review Issue is ready to be reviewed by a maintainer label Jun 28, 2023
@tomekzaw
Copy link
Member

tomekzaw commented Jun 28, 2023

Hey @danielRChildrens, thanks for reporting this issue.

Currently, our initialization flow for iOS assumes that AppDelegate extends RCTAppDelegate. We swizzle jsExecutorFactoryForBridge method in order to call reanimated::REAInitializer(bridge) at the early stage of app startup (see RCTAppDelegate+Reanimated.mm).

Please let me know if any of the approaches below works for you:

  1. Make changes to AppDelegate so that it extends RCTAppDelegate
-class AppDelegate: UIResponder, UIApplicationDelegate {
+class AppDelegate: RCTAppDelegate, UIResponder, UIApplicationDelegate {
  1. Override method jsExecutorFactoryForBridge and manually call reanimated::REAInitializer(bridge)

@tapz
Copy link

tapz commented Jun 29, 2023

@tomekzaw Could you please give more information about the approach 2? Tried to make my AppDelegate.swift to implement RCTCxxBridgeDelegate, but got error "Cannot find type 'RCTCxxBridgeDelegate' in scope". Tried to add import React, but that did not help.

@tomekzaw
Copy link
Member

@tapz I'm a Swift beginner so I don't think I can help much in this case. Basically, you need to call reanimated::REAInitializer(bridge) function somewhere at the beginning of the initialization flow of the app.

@tapz
Copy link

tapz commented Jun 29, 2023

@tomekzaw RCTAppDelegate does not support Swift, so probably RCTCxxBridgeDelegate doesn't either. Do you have any solution that would work with Swift? Otherwise Reanimated 3 does not work with Swift at all.

@tapz
Copy link

tapz commented Jun 29, 2023

@tomekzaw Ok, I'll try to add it to as early as possible in the app initialization.

@tomekzaw
Copy link
Member

@tapz We're currently working on a better solution so hopefully Reanimated 3.4.0 will no longer use this mechanism.

@tapz
Copy link

tapz commented Jun 29, 2023

The problem is that I don't know how to call reanimated::REAInitializer(bridge) from Swift. I have the bridge object, as I instantiate it myself. But not the error is

Cannot find 'reanimated' in scope
Consecutive statements on a line must be separated by ';'
Expected expression

or if I remove reanimated::

"Cannot find type 'REAInitializer' in scope"

Probably have to write some kind of Objective-c bridge to make the call.

@tomekzaw
Copy link
Member

Yup, reanimated::REAInitializer is a function written in Objective-C++.

@tapz
Copy link

tapz commented Jun 29, 2023

I created a bridge, but now the app crashes to this:

Exception NSException * "[<RCTBridge 0x28357dce0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key _callableJSModules." 0x00000002810c2e20

REAnimatedBridge.h

#import <Foundation/Foundation.h>
#import <React/RCTBridge.h>

@interface REAnimatedBridge : NSObject

- (void) rainit:(RCTBridge *)bridge;


@end

REAnimatedBridge.mpp

#import <RNReanimated/REAInitializer.h>
#import "REAnimatedBridge.h"

@implementation REAnimatedBridge

- (void) rainit:(RCTBridge *)bridge {
  reanimated::REAInitializer(bridge);
}

@end

MyApp-Bridging-Header.m

#import "REAnimatedBridge.h"

AppDelegate.swift

REAnimatedBridge().rainit(bridge)

rainit probably could be static, so there would be no need to create an instance of REAnimatedBridge.

@tomekzaw
Copy link
Member

@tapz What do you mean by "I created a bridge"? You need to pass the same instance of bridge which has already been created by React Native.

@tapz
Copy link

tapz commented Jun 30, 2023

@tomekzaw Since I'm not using RCTAppDelegate, I need to create the bridge myself.

guard let bridge = RCTBridge(delegate: self, launchOptions: launchOptions) else { return false }
let rootView = RCTRootView(bridge: bridge, moduleName: "MyApp", initialProperties: [:])
rootView.loadingView = loadingView
...
let rootViewController = UIViewController()
rootViewController.view = rootView
window.rootViewController = rootViewController
window.makeKeyAndVisible()

@zack2012
Copy link

zack2012 commented Jul 13, 2023

@tapz try below code. It is work for me.

import UIKit
import React

class RNBaseViewController: UIViewController {

  init(moduleName: String, initialProperties: [AnyHashable : Any]? = nil) {
    hacker = RNObjcHacker()
    let bridge = RCTBridge(delegate: hacker, launchOptions: nil)!
    rootView = RCTRootView(bridge: bridge, moduleName: moduleName, initialProperties: initialProperties)
    super.init(nibName: nil, bundle: nil)
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  let hacker: RNObjcHacker
  let rootView: RCTRootView
  
  override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    addRCTRootView()
  }
    
  func addRCTRootView() {
    rootView.translatesAutoresizingMaskIntoConstraints = false
    self.view.addSubview(rootView)
    NSLayoutConstraint.activate([
      rootView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
      rootView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
      rootView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
      rootView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
    ])
  }
}
// RNObjcHacker.h file
#import <Foundation/Foundation.h>
#import <React/RCTBridge.h>

NS_ASSUME_NONNULL_BEGIN

@interface RNObjcHacker : NSObject<RCTBridgeDelegate>

@end

NS_ASSUME_NONNULL_END

// RNObjcHacker.mm file
#import "RNObjcHacker.h"
#import <React/RCTRuntimeExecutorFromBridge.h>
#import <react/renderer/runtimescheduler/RuntimeScheduler.h>
#import <React/RCTCxxBridgeDelegate.h>
#import <RCTAppSetupUtils.h>
#import <RNReanimated/REAInitializer.h>
#import <React/RCTBundleURLProvider.h>

@interface RNObjcHacker () <RCTCxxBridgeDelegate> {
  std::shared_ptr<facebook::react::RuntimeScheduler> _runtimeScheduler;
}
@end

@implementation RNObjcHacker

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

- (BOOL)runtimeSchedulerEnabled
{
  return YES;
}

#pragma mark - RCTCxxBridgeDelegate
- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:(RCTBridge *)bridge
{
  reanimated::REAInitializer(bridge);

  if (self.runtimeSchedulerEnabled) {
    _runtimeScheduler = std::make_shared<facebook::react::RuntimeScheduler>(RCTRuntimeExecutorFromBridge(bridge));
  }
  return RCTAppSetupJsExecutorFactoryForOldArch(bridge, _runtimeScheduler);
}

@end

@tapz
Copy link

tapz commented Aug 3, 2023

@tomekzaw I updated from 3.3.0 to 3.4.1 and now reanimated::REAInitializer cannot be found.

REAnimatedBridge.mpp:15:15 No type named 'REAInitializer' in namespace 'reanimated'

@tapz
Copy link

tapz commented Aug 3, 2023

Does not seem to require the REAInitializer call anymore...

@piaskowyk
Copy link
Member

yes, you no longer need to call REAInitializer

github-merge-queue bot pushed a commit that referenced this issue Aug 4, 2023
## Summary

Fixes
#4631

In #4619
I removed REAInitializer because it is no longer needed. However, some
brownfield apps still use it. This PR adds a mock for REAInitializer to
ensure backward compatibility.

## Test plan

CI
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snippet of code, snack or repo is provided
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants