voize-gmbh / reakt-native-toolkit

Combine React Native with Kotlin Multiplatform (KMP)
Apache License 2.0
134 stars 5 forks source link

[Question] Obtaining env at initialization time #68

Closed rocketraman closed 2 months ago

rocketraman commented 2 months ago

Our RN native app uses react-native-dotenv to inject environment variables at build time into the RN app.

One use of these variables is defining the base URLs for API endpoints.

I would like my RN module to make use of these values as well. However, it doesn't appear as if RN provides any easy way to pass such parameters at module initialization time.

What is the right approach to deal with this sort of thing?

My first though is to expose a @ReactNativeMethod which receives the configuration, and sets some lazy or nullable vars in the module. This works but it is a bit messy as the initialization and operation of various components in the module race with the setting of these values, so code in the module needs to consider this.

Another option is to compile a version of the module for each environment, and then at RN app build time, choose the appropriate version as a dependency. I'm not fond of this either.

Another approach is to build the module in the same build pipeline as the React Native app, so that something like BuildConfig can be used to obtain the env values in a similar fashion to RN and react-native-dotenv. This seems like the best option, of the options I've outlined.

Thoughts welcome.

Legion2 commented 2 months ago

We use https://github.com/yshrsmz/BuildKonfig to inject build config in multiplatform applications, so it is available in common code and can be injected into the RN modules via constructor an initialisation time.

rocketraman commented 1 month ago

For future searchers, using a tool like BuildConfig wasn't a great option for us, as we wanted to publish our native module and use it as a library. Given that the consuming RN app has its own configuration, we would have had to synchronize the build-time configuration of the RN app with the library, or build the library as part of the build of the RN app. This adds significant complexity.

We therefore decided to create a build config plugin in our RN Expo app which publishes the app's current configuration to the Android strings.xml and iOS Info.plist files, and then we obtain these values in our Kotlin RN module at runtime. This also has the advantage of making these values part of our DI graph, which makes it easier to inject them during tests as opposed to statically accessed BuildConfig generated code.

Legion2 commented 1 month ago

You know that you can use DI with reakt-native-toolkit, to inject anything you like at RN Module construction on native side. So you can use the BuildKonfig to generate static config in the app project and inject this into the RN Module in your lib.

rocketraman commented 1 month ago

You know that you can use DI with reakt-native-toolkit, to inject anything you like at RN Module construction on native side. So you can use the BuildKonfig to generate static config in the app project and inject this into the RN Module in your lib.

Yep I'm aware and already use DI in my module, but BuildKonfig generates objects so replacing it in unit tests is non-trivial -- the Gradle file becomes the source of truth rather than a runtime implementation of some interface or data class instance.

Anyway, DI wasn't the main point of my post. I was simply offering an alternative when one doesn't want to tie one's published module to build-time vars.

rocketraman commented 1 month ago

So you can use the BuildKonfig to generate static config in the app project and inject this into the RN Module in your lib.

Ah, I missed "in the app project". Did you really mean this because that doesn't seem possible? If it were, I wouldn't need reakt-native :-)

Legion2 commented 1 month ago

You can define an interface or data class for your config in the lib project. The RN Module takes this as Configuration in the constructor. In the app you need to instanceate this interface or data class. For this you can either use the BuildKonfig and create a converter or use your approach with Config files and create a config Reader. You can also use a different approach to construct the interface and pass it to the lib RN Module.

rocketraman commented 1 month ago

In the app you need to instanceate this interface or data class.

If I understand correctly, this would mean modifying the iOS AppDelegate.mm and Android MainApplication.kt to create and pass in this interface/data class at module init time? If so, then I think I still prefer my approach -- its basically the same thing, but I create the config class in shared code at initialization time on the lib side, rather than in platform-specific code on the app side. In cases where the configuration can't be passed to the lib via resources for some reason, then injecting the config itself from the app makes total sense though.

The rest is the same -- my config is injected into the DI graph and passed into the constructors of @ReactNativeModule classes.

Legion2 commented 1 month ago

you can instantiate the DI graph in common code and also instantiate the RNModules in common code in your app (if your app is using kmp) the toolkit already provides the ReactNativeModuleProvider interface and also generates common code Provider for all you RNModuels, so you can construct them (...RNModuleProvider) in common code instead of instantiating ...RNModuleAndroid and ...RNModuleiOS seperatly in platform speficic code.

rocketraman commented 1 month ago

you can instantiate the DI graph in common code and also instantiate the RNModules in common code in your app (if your app is using kmp)

My app is a raw React Native app (via Expo) that does not directly use KMP. Rather, it imports my KMP code as an Android library / iOS framework.