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

Enhanced lifecycle-aware module registry #849

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 202 additions & 0 deletions proposals/0000-enhanced-lifecycle-aware-module-registry.md
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
Comment on lines +52 to +60

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. And UISceneDelegate is the preffered way of handling this from iOS 13+

How would you handle it?

```

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
Copy link

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

The 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

Copy link
Author

Choose a reason for hiding this comment

The 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 inRCTAppDelegate, so there’s no need for user-facing changes. I’ll revise the examples to avoid confusion for others.

```

### Android Implementation

Define an interface for lifecycle-aware modules:

```kotlin
interface RNLifecycleAwareModule {
fun onCreate(application: Application)
Copy link

Choose a reason for hiding this comment

The 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()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could invalidate already be used to provide the same information here?

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

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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 onCreate, onStart, onResume implementation and so on. Perhaps on Android it might be using a dependency injection framework to orchestrate those calls.

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 onCreate callback to work correctly, and call it directly.

As an alternative, we could provide a callback for brownfield users to invoke the LifecycleManager.notify() methods you mentioned in your examples. Still, this reduces flexibility and makes integration with brownfield apps harder


- **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?