-
Notifications
You must be signed in to change notification settings - Fork 131
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
Enhanced lifecycle-aware module registry #849
base: main
Are you sure you want to change the base?
Changes from 4 commits
0dbe206
656ee2c
5125322
c1e2dc7
9bf4433
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
--- | ||
title: Enhanced lifecycle-aware module registry | ||
author: | ||
- Matin Zadeh Dolatabad | ||
date: 2025-01-06 | ||
--- | ||
|
||
# RFC0000: Enhanced lifecycle-aware module registry | ||
|
||
## Summary | ||
|
||
This RFC proposes a framework to enhance the module registry in React Native by introducing a lifecycle-aware system for native modules. The goal is to address the current gap in handling application lifecycle events, similar to Flutter's `FlutterApplicationLifeCycleDelegate` on iOS and `Application.ActivityLifecycleCallbacks` on Android. The design enables seamless integration of native modules with application lifecycle events across iOS and Android platforms. There is also Expo Modules Core that handles this via `ExpoAppDelegateSubscriber` and `ReactActivityLifecycleListener`, but React Native does not have this by default and it requires Expo to be used in such cases. | ||
|
||
## Motivation | ||
|
||
React Native lacks a comprehensive and standardized approach for native module lifecycle management. Unlike Flutter, where plugins can hook into application lifecycle events via a well-defined protocol, React Native requires developers to implement custom solutions for such behavior. This limitation results in increased boilerplate code, fragmented implementations, and an inconsistent developer experience. | ||
|
||
### Key Challenges Addressed: | ||
|
||
- **Limited Module Registry:** React Native's module registry doesn't natively support lifecycle awareness. On Android, React Native provides a `LifecycleEventListener` that partially addresses this by allowing modules to listen to events like `onHostResume`, `onHostPause`, and `onHostDestroy`. However, there is no support for early lifecycle events such as `onCreate` or `onTerminate`. On iOS, there is no equivalent lifecycle support, leading to significant gaps in managing the full application lifecycle effectively across platforms. | ||
- **Manual Lifecycle Handling:** Developers must manually wire up lifecycle events for each native module. | ||
- **Inconsistency Across Platforms:** There is no unified approach for managing lifecycle events across iOS and Android except `Expo Modules API`. | ||
|
||
### Potential use cases: | ||
|
||
- One of the most popular libraries that can benefit from this approach is [React Native Firebase](https://github.com/invertase/react-native-firebase). For example, integrating Firebase on iOS requires adding `[FIRApp configure]` to the `didFinishLaunchingWithOptions` method in the `AppDelegate`. This process could be automated by allowing the Firebase module to implement a lifecycle-aware interface: | ||
```objc | ||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { | ||
[FIRApp configure]; | ||
// Other initialization code... | ||
return YES; | ||
} | ||
``` | ||
|
||
- Another libraries like [`react-native-health-connect`](https://github.com/matinzd/react-native-health-connect), [`@braze/react-native-sdk`](https://github.com/braze-inc/braze-react-native-sdk) can benefit from this approach without the need for manual setup and the headache of managing issues related to lifecycle events. | ||
|
||
By supporting lifecycle-aware modules, React Native Firebase and similar libraries can eliminate the need for manual native code changes, making integration smoother and more developer-friendly. | ||
|
||
## Detailed Design | ||
|
||
The proposed solution introduces a `LifecycleAwareModule` interface and a centralized lifecycle manager for React Native. This framework would: | ||
|
||
1. Allow native modules to register as lifecycle-aware. | ||
2. Notify registered modules of key application lifecycle events. | ||
3. Provide a consistent API across platforms. | ||
|
||
### iOS Implementation | ||
|
||
Define a protocol for lifecycle-aware modules: | ||
|
||
```objc | ||
@protocol RNLifecycleAwareModule <NSObject> | ||
@optional | ||
- (void)applicationDidFinishLaunching:(UIApplication *)application; | ||
- (void)applicationWillResignActive:(UIApplication *)application; | ||
- (void)applicationDidEnterBackground:(UIApplication *)application; | ||
- (void)applicationWillEnterForeground:(UIApplication *)application; | ||
- (void)applicationDidBecomeActive:(UIApplication *)application; | ||
- (void)applicationWillTerminate:(UIApplication *)application; | ||
@end | ||
``` | ||
|
||
Create a `RNLifecycleManager` to manage registered modules: | ||
|
||
```objc | ||
@interface RNLifecycleManager : NSObject | ||
+ (instancetype)sharedInstance; | ||
- (void)registerModule:(id<RNLifecycleAwareModule>)module; | ||
- (void)notifyModulesWithSelector:(SEL)selector application:(UIApplication *)application; | ||
@end | ||
Comment on lines
+66
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not be a singleton (neither in iOS or Android). React Native modules are always instance-scoped. If you reload your application, all your native modules will be re-created, and so any singleton referencing them like this is going to cause a memory leak and other problems. |
||
``` | ||
|
||
Hook into `AppDelegate`: | ||
|
||
```objc | ||
@implementation AppDelegate | ||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { | ||
[[RNLifecycleManager sharedInstance] notifyModulesWithSelector:@selector(applicationDidFinishLaunching:) application:application]; | ||
return YES; | ||
} | ||
@end | ||
Comment on lines
+76
to
+81
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps it would be best if this was internal in RN itself (maybe via method swizzling or having some standard interface from core that is exposed and the app delegate implements) this way we can make changes without any user facing breaking API There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My main goal is simply to show, in basic terms, how this works. The actual implementation will occur internally in |
||
``` | ||
|
||
### Android Implementation | ||
|
||
Define an interface for lifecycle-aware modules: | ||
|
||
```kotlin | ||
interface RNLifecycleAwareModule { | ||
fun onCreate(application: Application) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How is this different from the module's constructor? On Android, no code runs before the Application object is created anyway. |
||
fun onResume() | ||
fun onPause() | ||
fun onStop() | ||
fun onDestroy() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could Right now I don't believe we explicitly hook that up to Application#onDestroy, but we could. |
||
} | ||
``` | ||
|
||
Create a `LifecycleManager` to manage registered modules: | ||
|
||
```kotlin | ||
object LifecycleManager { | ||
private val modules = mutableListOf<RNLifecycleAwareModule>() | ||
matinzd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
fun registerModule(module: RNLifecycleAwareModule) { | ||
modules.add(module) | ||
} | ||
|
||
fun notify(event: (RNLifecycleAwareModule) -> Unit) { | ||
modules.forEach { event(it) } | ||
} | ||
} | ||
``` | ||
|
||
Hook into `Application`: | ||
|
||
```kotlin | ||
class MainApplication : Application() { | ||
override fun onCreate() { | ||
super.onCreate() | ||
LifecycleManager.notify { it.onCreate(this) } | ||
} | ||
|
||
override fun onTerminate() { | ||
super.onTerminate() | ||
LifecycleManager.notify { it.onDestroy() } | ||
} | ||
} | ||
Comment on lines
+117
to
+127
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here |
||
``` | ||
|
||
### Usage Inside Libraries | ||
|
||
Libraries can leverage the lifecycle-aware module registry to handle lifecycle events seamlessly. For instance: | ||
|
||
#### iOS Example | ||
|
||
```objc | ||
@interface MyModule : NSObject <RNLifecycleAwareModule> | ||
@end | ||
|
||
@implementation MyModule | ||
|
||
- (void)applicationDidFinishLaunching:(UIApplication *)application { | ||
// Initialize resources | ||
} | ||
|
||
- (void)applicationDidEnterBackground:(UIApplication *)application { | ||
// Save state | ||
} | ||
|
||
@end | ||
``` | ||
|
||
#### Android Example | ||
|
||
```kotlin | ||
class MyModule : RNLifecycleAwareModule { | ||
override fun onCreate(application: Application) { | ||
// Initialize resources | ||
} | ||
|
||
override fun onPause() { | ||
// Save state | ||
} | ||
} | ||
``` | ||
|
||
By implementing the `RNLifecycleAwareModule` interface, library developers can ensure that their modules respond appropriately to lifecycle events without requiring additional setup from application developers. This approach streamlines integration, reducing manual coding and improving consistency across projects. | ||
|
||
While the design details here serve as a conceptual demonstration, the actual implementation may differ based on React Native's evolving architecture. For instance, this could either extend the current module registry or involve introducing a new interface entirely, which may cause breaking changes in some scenarios. These considerations would require careful evaluation during the development phase. | ||
|
||
## Drawbacks | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One significant drawbacks that I see here is that it will be harder for brownfield users to integrate a lifecycle aware module inside their apps. That's because a brownfield app will most likely already have his own By implementing this proposals, we'll be hiding away some of the configuration needed for a module to work correctly. So brownfield users will have to discover which modules need an As an alternative, we could provide a callback for brownfield users to invoke the |
||
|
||
- **Backward Compatibility:** Existing modules will need updates to implement the lifecycle-aware interface. | ||
- **Performance Overhead:** Centralized lifecycle management may introduce performance overhead, especially with many registered modules. | ||
- **Potential Conflicts:** Modules that handle lifecycle events internally may conflict with the centralized lifecycle manager. | ||
|
||
## Alternatives | ||
|
||
### Option 1 | ||
|
||
Use third-party libraries for lifecycle management, but this lacks standardization and official support. | ||
|
||
### Option 2 | ||
|
||
Continue with the current approach, leaving lifecycle management to individual frameworks/developers. | ||
|
||
## Adoption Strategy | ||
|
||
- Introduce as an optional feature in a minor React Native release. | ||
- Provide detailed documentation and examples. | ||
- Encourage library authors to adopt the lifecycle-aware interface for their modules. | ||
|
||
## How We Teach This | ||
|
||
- Update official documentation with lifecycle-aware module examples. | ||
- Provide tutorials and migration guides for developers. | ||
- Offer webinars or workshops to explain the new system. | ||
|
||
## Unresolved Questions | ||
|
||
- How to handle potential performance overhead with many registered modules? | ||
- How to ensure compatibility with existing React Native libraries? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue I see with this is the migration to
UISceneDelegate
. There are a lot of additional lifecycle events. AndUISceneDelegate
is the preffered way of handling this from iOS 13+How would you handle it?