Skip to content

HeadlessJsTaskService getReactNativeHost override #342

@naquilini

Description

@naquilini

Environment

System:
  OS: macOS 13.6
  CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
  Memory: 137.46 MB / 16.00 GB
  Shell:
    version: 3.2.57
    path: /bin/bash
Binaries:
  Node:
    version: 18.20.8
    path: ~/.nvm/versions/node/v18.20.8/bin/node
  Yarn:
    version: 1.22.19
    path: /usr/local/bin/yarn
  npm:
    version: 10.8.2
    path: ~/.nvm/versions/node/v18.20.8/bin/npm
  Watchman:
    version: 2024.12.02.00
    path: /usr/local/bin/watchman
Managers:
  CocoaPods:
    version: 1.15.2
    path: /usr/local/bin/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 23.2
      - iOS 17.2
      - macOS 14.2
      - tvOS 17.2
      - visionOS 1.0
      - watchOS 10.2
  Android SDK: Not Found
IDEs:
  Android Studio: 2024.3 AI-243.26053.27.2432.13536105
  Xcode:
    version: 15.2/15C500b
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.15
    path: /Users/ipaddev/.jenv/shims/javac
  Ruby:
    version: 2.6.10
    path: /usr/bin/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 18.0.0
    wanted: 18.0.0
  react:
    installed: 19.0.0
    wanted: 19.0.0
  react-native:
    installed: 0.79.5
    wanted: 0.79.5
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: false
  newArchEnabled: false
iOS:
  hermesEnabled: false
  newArchEnabled: false

Things I’ve done to figure out my issue

Upgrading version

0.79.5

Description

I'm upgrading from RN 0.78.3 to RN 0.79.5.
I have a custom module to handle push notifications.
On Android I implemented a background service which extends HeadlessJsTaskService and overrides getReactNativeHost, as my application does not implement ReactApplication.
When upgrading to RN 0.79.5 I get a build error as getReactNativeHost since this RN version has been marked as final:

error: getReactNativeHost() in PushHeadless cannot override getReactNativeHost() in HeadlessJsTaskService protected ReactNativeHost getReactNativeHost() { ^ overridden method is final

I checked HeadlessJsTaskService.kt source file (https://github.com/facebook/react-native/blob/v0.79.5/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/HeadlessJsTaskService.kt) and it still reports this in the comments:

/**
   * Get the [ReactNativeHost] used by this app. By default, assumes [getApplication] is an instance
   * of [ReactApplication] and calls [ReactApplication.reactNativeHost].
   *
   * Override this method if your application class does not implement `ReactApplication` or you
   * simply have a different mechanism for storing a `ReactNativeHost`, e.g. as a static field
   * somewhere.
   */
  protected val reactNativeHost: ReactNativeHost
    get() = (application as ReactApplication).reactNativeHost

But in ReactAndroid.api (https://github.com/facebook/react-native/blob/v0.79.5/packages/react-native/ReactAndroid/api/ReactAndroid.api), the method is declared as follows:

public abstract class com/facebook/react/HeadlessJsTaskService : android/app/Service, com/facebook/react/jstasks/HeadlessJsTaskEventListener {
	...
	protected final fun getReactNativeHost ()Lcom/facebook/react/ReactNativeHost;
	...
}

Is this comment still accurate?
Is there still a way to override getReactNativeHost in custom headless tasks?

Reproducible demo

Create custom Task service which extend HeadlessJsTaskService and try to override method getReactNativeHost

Example:

import android.app.Application;
import android.content.Intent;
import android.os.Bundle;

import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;

import java.lang.reflect.InvocationTargetException;
import javax.annotation.Nullable;

/**
 * called from PushMessagingService if app is closed or in background
 */
public class PushHeadless extends HeadlessJsTaskService {
	private static final long TIMEOUT_DEFAULT = 60000;

	@Override
	protected ReactNativeHost getReactNativeHost() {
		return PushReactHost.getReactNativeHost(getApplication());
	}

	@Override
	protected @Nullable
	HeadlessJsTaskConfig getTaskConfig(Intent intent) {
		try {
			if (intent == null) {
				return null;
			}

			Bundle extras = intent.getExtras();
			if (extras == null) {
				return null;
			}

			WritableMap messageMap = ((Push) intent.getParcelableExtra(PushHandler.MESSAGE)).toMap();
			if(messageMap != null) {
				// Prevents race condition where the user opens the app at the same time as a notification
				// is delivered, causing a crash.
				return new HeadlessJsTaskConfig(getTaskName(), messageMap, TIMEOUT_DEFAULT, true);
			}
		} catch (Exception e){
			e.printStackTrace();
		}
		return null;
	}

	protected String getTaskName(){
		return "TaHeadlessTask";
	}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions