From dd7595b0a6332cc3f94bc5f4e0df2caea8b2663c Mon Sep 17 00:00:00 2001
From: Mateusz Rajski <mateusz.rajski@swmansion.com>
Date: Tue, 6 May 2025 14:16:05 +0200
Subject: [PATCH 1/5] Fallback to old EventEmitter API on Android

---
 .../ReactNativeBackgroundTaskModule.kt              | 13 ++++++++++++-
 .../src/NativeReactNativeBackgroundTask.ts          |  2 --
 modules/background-task/src/index.ts                |  8 ++++++--
 3 files changed, 18 insertions(+), 5 deletions(-)

diff --git a/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskModule.kt b/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskModule.kt
index 19d3e31d83c1..d6b681a24e86 100644
--- a/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskModule.kt
+++ b/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskModule.kt
@@ -14,15 +14,26 @@ import android.content.IntentFilter
 import android.os.Build
 import android.os.PersistableBundle
 import android.util.Log
+import com.facebook.react.modules.core.DeviceEventManagerModule
+import com.facebook.react.bridge.WritableMap
+import com.facebook.react.bridge.Arguments
 
 class ReactNativeBackgroundTaskModule internal constructor(context: ReactApplicationContext) :
   ReactNativeBackgroundTaskSpec(context) {
 
+  private fun sendEvent(eventName: String, params: WritableMap?) {
+    reactApplicationContext
+      .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
+      .emit(eventName, params)
+  }
+
   private val taskReceiver = object : BroadcastReceiver() {
     override fun onReceive(context: Context?, intent: Intent?) {
       val taskName = intent?.getStringExtra("taskName")
       Log.d("ReactNativeBackgroundTaskModule", "Received task: $taskName")
-      emitOnBackgroundTaskExecution(taskName)
+      val params = Arguments.createMap()
+      params.putString("taskName", taskName)
+      sendEvent("onBackgroundTaskExecution", params)
     }
   }
 
diff --git a/modules/background-task/src/NativeReactNativeBackgroundTask.ts b/modules/background-task/src/NativeReactNativeBackgroundTask.ts
index 792fe2850552..15aba09bef0c 100644
--- a/modules/background-task/src/NativeReactNativeBackgroundTask.ts
+++ b/modules/background-task/src/NativeReactNativeBackgroundTask.ts
@@ -1,12 +1,10 @@
 import type {TurboModule} from 'react-native';
 import {TurboModuleRegistry} from 'react-native';
-import type {EventEmitter} from 'react-native/Libraries/Types/CodegenTypes';
 
 // We need to export the interface inline for proper TypeScript type inference with TurboModules
 // eslint-disable-next-line rulesdir/no-inline-named-export, @typescript-eslint/consistent-type-definitions
 export interface Spec extends TurboModule {
     defineTask(taskName: string, taskExecutor: (data: unknown) => void | Promise<void>): Promise<void>;
-    readonly onBackgroundTaskExecution: EventEmitter<string>;
 }
 
 export default TurboModuleRegistry.getEnforcing<Spec>('ReactNativeBackgroundTask');
diff --git a/modules/background-task/src/index.ts b/modules/background-task/src/index.ts
index d3afe75a85bc..462421ea44e5 100644
--- a/modules/background-task/src/index.ts
+++ b/modules/background-task/src/index.ts
@@ -1,16 +1,20 @@
+import { DeviceEventEmitter, EmitterSubscription } from 'react-native';
 import NativeReactNativeBackgroundTask from './NativeReactNativeBackgroundTask';
 
 type TaskManagerTaskExecutor<T = unknown> = (data: T) => void | Promise<void>;
 
 const taskExecutors = new Map<string, TaskManagerTaskExecutor>();
 
-NativeReactNativeBackgroundTask.onBackgroundTaskExecution((taskName) => {
+function onBackgroundTaskExecution({taskName}: {taskName: string}) {
+    console.log('onBackgroundTaskExecution', taskName);
     const executor = taskExecutors.get(taskName);
 
     if (executor) {
         executor(taskName);
     }
-});
+}
+
+const backgroundTasksSubscription = DeviceEventEmitter.addListener('onBackgroundTaskExecution', onBackgroundTaskExecution);
 
 const TaskManager = {
     /**

From 5216182e7aea4b2f2a2a041e598aef5ece423341 Mon Sep 17 00:00:00 2001
From: Mateusz Rajski <mateusz.rajski@swmansion.com>
Date: Tue, 6 May 2025 17:58:05 +0200
Subject: [PATCH 2/5] Use old API on iOS

---
 .../ReactNativeBackgroundTaskModule.kt             |  8 ++++++++
 .../ios/ReactNativeBackgroundTask.h                |  5 +++--
 .../ios/ReactNativeBackgroundTask.mm               |  9 ++++++++-
 .../src/NativeReactNativeBackgroundTask.ts         |  2 ++
 modules/background-task/src/index.ts               | 14 +++++++++++---
 5 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskModule.kt b/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskModule.kt
index d6b681a24e86..6b0f81c0e92d 100644
--- a/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskModule.kt
+++ b/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskModule.kt
@@ -91,6 +91,14 @@ class ReactNativeBackgroundTaskModule internal constructor(context: ReactApplica
     }
   }
 
+  override fun addListener(eventType: String?) {
+    // no-op
+  }
+
+  override fun removeListeners(count: Double) {
+    // no-op
+  }
+
   companion object {
     const val NAME = "ReactNativeBackgroundTask"
   }
diff --git a/modules/background-task/ios/ReactNativeBackgroundTask.h b/modules/background-task/ios/ReactNativeBackgroundTask.h
index 8f3f0e114501..054f1019157d 100644
--- a/modules/background-task/ios/ReactNativeBackgroundTask.h
+++ b/modules/background-task/ios/ReactNativeBackgroundTask.h
@@ -1,13 +1,14 @@
+#import <React/RCTEventEmitter.h>
 #ifdef RCT_NEW_ARCH_ENABLED
 #import <ReactCodegen/RNReactNativeBackgroundTaskSpec/RNReactNativeBackgroundTaskSpec.h>
 #import <BackgroundTasks/BackgroundTasks.h>
 
-@interface ReactNativeBackgroundTask : NativeReactNativeBackgroundTaskSpecBase <NativeReactNativeBackgroundTaskSpec>
+@interface ReactNativeBackgroundTask : RCTEventEmitter <NativeReactNativeBackgroundTaskSpec>
 #else
 #import <React/RCTBridgeModule.h>
 #import <BackgroundTasks/BackgroundTasks.h>
 
-@interface ReactNativeBackgroundTask : NSObject <RCTBridgeModule>
+@interface ReactNativeBackgroundTask : RCTEventEmitter <RCTBridgeModule>
 #endif
 
 - (void)defineTask:(NSString *)taskName
diff --git a/modules/background-task/ios/ReactNativeBackgroundTask.mm b/modules/background-task/ios/ReactNativeBackgroundTask.mm
index f963c928a41e..68f9e4968fc6 100644
--- a/modules/background-task/ios/ReactNativeBackgroundTask.mm
+++ b/modules/background-task/ios/ReactNativeBackgroundTask.mm
@@ -73,7 +73,7 @@ - (BOOL)scheduleNewBackgroundTask:(NSString *)identifier error:(NSError **)outEr
             // Execute all registered tasks
             [self->_taskExecutors enumerateKeysAndObjectsUsingBlock:^(NSString *taskName, RCTResponseSenderBlock executor, BOOL *stop) {
                 NSLog(@"[ReactNativeBackgroundTask] Executing task: %@", taskName);
-                [self emitOnBackgroundTaskExecution:(taskName)];
+                [self sendEventWithName:@"onBackgroundTaskExecution" body:@{@"taskName": taskName}];
             }];
             
             NSError *scheduleError = nil;
@@ -103,6 +103,13 @@ - (BOOL)scheduleNewBackgroundTask:(NSString *)identifier error:(NSError **)outEr
     }
 
     _taskExecutors[taskName] = taskExecutor;
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+        [self sendEventWithName:@"onBackgroundTaskExecution" body:@{@"taskName": taskName}];
+    });
+}
+
+- (NSArray<NSString *> *)supportedEvents {
+  return @[@"onBackgroundTaskExecution"];
 }
 
 // Don't compile this code when we build for the old architecture.
diff --git a/modules/background-task/src/NativeReactNativeBackgroundTask.ts b/modules/background-task/src/NativeReactNativeBackgroundTask.ts
index 15aba09bef0c..737d783d9634 100644
--- a/modules/background-task/src/NativeReactNativeBackgroundTask.ts
+++ b/modules/background-task/src/NativeReactNativeBackgroundTask.ts
@@ -5,6 +5,8 @@ import {TurboModuleRegistry} from 'react-native';
 // eslint-disable-next-line rulesdir/no-inline-named-export, @typescript-eslint/consistent-type-definitions
 export interface Spec extends TurboModule {
     defineTask(taskName: string, taskExecutor: (data: unknown) => void | Promise<void>): Promise<void>;
+    addListener: (eventType: string) => void;
+    removeListeners: (count: number) => void;
 }
 
 export default TurboModuleRegistry.getEnforcing<Spec>('ReactNativeBackgroundTask');
diff --git a/modules/background-task/src/index.ts b/modules/background-task/src/index.ts
index 462421ea44e5..c91c96add614 100644
--- a/modules/background-task/src/index.ts
+++ b/modules/background-task/src/index.ts
@@ -1,12 +1,13 @@
-import { DeviceEventEmitter, EmitterSubscription } from 'react-native';
+import { DeviceEventEmitter, EmitterSubscription, NativeEventEmitter } from 'react-native';
 import NativeReactNativeBackgroundTask from './NativeReactNativeBackgroundTask';
 
 type TaskManagerTaskExecutor<T = unknown> = (data: T) => void | Promise<void>;
 
+const eventEmitter = new NativeEventEmitter(NativeReactNativeBackgroundTask);
 const taskExecutors = new Map<string, TaskManagerTaskExecutor>();
+let subscription: EmitterSubscription | undefined;
 
 function onBackgroundTaskExecution({taskName}: {taskName: string}) {
-    console.log('onBackgroundTaskExecution', taskName);
     const executor = taskExecutors.get(taskName);
 
     if (executor) {
@@ -14,7 +15,12 @@ function onBackgroundTaskExecution({taskName}: {taskName: string}) {
     }
 }
 
-const backgroundTasksSubscription = DeviceEventEmitter.addListener('onBackgroundTaskExecution', onBackgroundTaskExecution);
+function addBackgroundTaskListener() {
+    if (subscription) {
+        subscription.remove();
+    }
+    subscription = eventEmitter.addListener('onBackgroundTaskExecution', onBackgroundTaskExecution);
+}
 
 const TaskManager = {
     /**
@@ -36,4 +42,6 @@ const TaskManager = {
     },
 };
 
+addBackgroundTaskListener();
+
 export default TaskManager;

From c7dc7df995551139840bc764a132b5c2b8eb3b4c Mon Sep 17 00:00:00 2001
From: Mateusz Rajski <mateusz.rajski@swmansion.com>
Date: Tue, 6 May 2025 17:59:21 +0200
Subject: [PATCH 3/5] Remove delayed event

---
 modules/background-task/ios/ReactNativeBackgroundTask.mm | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/modules/background-task/ios/ReactNativeBackgroundTask.mm b/modules/background-task/ios/ReactNativeBackgroundTask.mm
index 68f9e4968fc6..861168ae508f 100644
--- a/modules/background-task/ios/ReactNativeBackgroundTask.mm
+++ b/modules/background-task/ios/ReactNativeBackgroundTask.mm
@@ -103,9 +103,6 @@ - (BOOL)scheduleNewBackgroundTask:(NSString *)identifier error:(NSError **)outEr
     }
 
     _taskExecutors[taskName] = taskExecutor;
-    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
-        [self sendEventWithName:@"onBackgroundTaskExecution" body:@{@"taskName": taskName}];
-    });
 }
 
 - (NSArray<NSString *> *)supportedEvents {

From 6ee9ba6cd4ff757cd7ebb3f8a8aa555783e88764 Mon Sep 17 00:00:00 2001
From: Mateusz Rajski <mateusz.rajski@swmansion.com>
Date: Wed, 7 May 2025 10:58:19 +0200
Subject: [PATCH 4/5] Fix lint

---
 modules/background-task/src/index.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/modules/background-task/src/index.ts b/modules/background-task/src/index.ts
index c91c96add614..65f55d374180 100644
--- a/modules/background-task/src/index.ts
+++ b/modules/background-task/src/index.ts
@@ -1,4 +1,5 @@
-import { DeviceEventEmitter, EmitterSubscription, NativeEventEmitter } from 'react-native';
+import type { EmitterSubscription } from 'react-native';
+import { NativeEventEmitter } from 'react-native';
 import NativeReactNativeBackgroundTask from './NativeReactNativeBackgroundTask';
 
 type TaskManagerTaskExecutor<T = unknown> = (data: T) => void | Promise<void>;

From c8ba52fc9b31aa9bdd5239eadabeca85b5fec603 Mon Sep 17 00:00:00 2001
From: Mateusz Rajski <mateusz.rajski@swmansion.com>
Date: Wed, 7 May 2025 11:07:24 +0200
Subject: [PATCH 5/5] Fix prettier

---
 modules/background-task/src/index.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/modules/background-task/src/index.ts b/modules/background-task/src/index.ts
index 65f55d374180..d1254b991eed 100644
--- a/modules/background-task/src/index.ts
+++ b/modules/background-task/src/index.ts
@@ -1,5 +1,5 @@
-import type { EmitterSubscription } from 'react-native';
-import { NativeEventEmitter } from 'react-native';
+import type {EmitterSubscription} from 'react-native';
+import {NativeEventEmitter} from 'react-native';
 import NativeReactNativeBackgroundTask from './NativeReactNativeBackgroundTask';
 
 type TaskManagerTaskExecutor<T = unknown> = (data: T) => void | Promise<void>;
