electrode-io/electrode-native

[Question] Sharing native dependencies across the container and native host

LcTwisk opened this issue · 6 comments

Hey!

I stumbled across a small problem when integrating a new MiniApp (into our iOS app, not sure yet if it's also a problem on the Android side). We're using the same native dependency, lottie-ios, in both our native host application and in our MiniApp. When building the container, ERN adds the dependency to the container (as expected). However, when building the host application, I now get multiple errors about commands producing the same files:

Multiple commands produce '<PROJECT_PATH>/Build/Intermediates.noindex/Pods.build/Debug-iphonesimulator/lottie-ios.build/all-product-headers.yaml':
1) Target 'lottie-ios' (project 'Pods') produces module 'lottie-ios'
2) Target 'lottie-ios' (project 'Pods') produces module 'lottie-ios'

Most likely because both the ElectrodeContainer.xcodeproj and our native project have lottie-ios added using Cocoapods. I've tried removing the dependency from the native app itself. However, now I'm not able to reference Lottie in code anymore...
I don't think this is a one of a kind problem, so there's probably a solution for it already (even though I couldn't find it in the docs).

@friederbluemle @belemaire Is there a way to share native dependencies across the native host and the container?

Hey @LcTwisk - I'm not too familiar with iOS dependency management. @jw2175 or @liulianci - Do you have any ideas how to solve this?
On Android, there are api and implementation scopes in build.gradle (implementation keeps the dependency contained, while api exposes it further downstream) - I assume something similar is possible in iOS. Ideally, if there is an explicit dependency on lottie-ios in the native host application, then the dependency should also be declared explicitly (rather than relying on the container to provide it). Of course that's only possible if the "Multiple commands produce" conflict can be solved via some mechanism..

Hi @LcTwisk , to reference Lottie, in the Build Settings of your app project, can you try updating the Header Search Paths to include the path to the headers of the Lottie framework?

Hi @jw2175! I have indeed tried modifying the Header Search Paths and Framework Search Paths. Even though it didn't work for me directly, this should be a possible solution. However, I don't think this is an ideal solution because of the following reasons:

  1. This would mean that I'd remove the host app's explicit dependency on Lottie. It feels weird and wrong only having the Lottie dependency on the container while it's being used in the host app itself.
  2. We're using Cocoapods for dependency management, which means the entries in Header Search Paths and Framework Search Paths are reconfigured on every pod install. This is not a biggy, we could potentially add custom entries in the Cocoapods post_install hook. However, this would mean that we're adding a complex workaround for all dependencies that we might share in the future. Wouldn't feel comfortable having to share this with my fellow colleagues 😅

While looking for other solutions I came across this blog post, mentioning a similar problem (same error, different setup). The blog post mentioned a Github issue, filed on the Cocoapods repository. Apparently, this build-time assertion was introduced in the 'New build system', shipped in Xcode 10 (which is the build system we're also using to build our app). I tried switching back to the 'Legacy build system' to see if that would make any difference, which it did, the build error didn't show up and I was able to build the project successfully.

As you might understand, switching back to the 'Legacy build system' is not the future-proof solution I'm looking for. The scary part of this solution was that the build succeeded while the host app and the container were relying on different versions of Lottie, while only 1 version would be copied over 😬 (something I discovered later on, this could have resulted in some nasty run-time crashes).

@friederbluemle I think the dependency management on iOS is less straightforward because there are quite a few different dependency managers out there (while on Android everyone uses Gradle nowadays).

Luckily, we're using Cocoapods in our host application, which is the same dependency manager that's being used for the container. However, I noticed that both the host application and the container were resolving dependencies independently from each other... (both have their own Podfile and generate their own Pods framework). This means that shared dependencies will never resolve into a single framework.

To overcome this problem I've added a .podspec file to the container, basically transforming the container from an independent project into a Cocoapods library. This allows me to add the container as a pod to the host app's target, like you'd do for any other dependency:

target 'HostApp' do
  pod 'ElectrodeContainer', :path => '<PATH_TO_LOCAL_CONTAINER>'
end

After adding the dependency to the Podfile, running pod install resulted in Cocoapods correctly resolving the dependencies across both the app and the container 🎉

I was able to achieve this by simply adding injecting the new.podspec file to the container hull at generation time. Additionally, I had to make small changes to some of the imports used in the container's source files.

Would be nice to contribute these changes back to the main repo, if you guys think this could be useful for others as well of course.
I noticed the container integration docs already have a 'Using Carthage' section, but no 'Using Cocoapods' section yet. Would be nice to add that as Cocoapods seems to be the most popular dependency manager.

Let me know what you think!

@LcTwisk Checkout our new documentation for integrating with Cocoapods: https://native.electrode.io/reference/index-1/container-integration#ios . It's now possible to add the iOS container dependency as a pod in client application.

thanks @belemaire !

@jw2175 Interesting, this is slightly different than what we’re doing. Wouldn’t including a pre-compiled binary mean that shared dependencies between native and React Native would be included twice?

Anyway, will try if this new integration method works in our setup and, if so, compare sizes of the compiled application binaries for both methods.

The pre-compiled binary doesn't fulfill our requirements so we'll keep using our internal solution to integrate the container into our existing Cocoapods setup. If you guys are ever interested in including a non-compiled version of the container using Cocoapods, happy to exchange some ideas 👍

Will close this for now.