Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
11.1.0
___

**Expo**
- Added configuration field `shouldUseNonNullableIntent` to control whether the MainActivity's `onNewIntent` method uses a nullable or non-nullable Intent parameter on Android. This is required for compatibility with AndroidX Activity 1.9+ which uses non-nullable Intent types. By default, it is set to `false` (nullable Intent) for backwards compatibility. Set it to `true` if you're using AndroidX Activity 1.9 or higher.


11.0.0
___

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public class RNBatchModuleImpl {

private static final String PLUGIN_VERSION_ENVIRONMENT_VARIABLE = "batch.plugin.version";

public static final String PLUGIN_VERSION = "ReactNative/11.0.0";
public static final String PLUGIN_VERSION = "ReactNative/11.1.0";

public static final String LOGGER_TAG = "RNBatchBridge";

Expand Down
2 changes: 2 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export default [...compat.extends(
argsIgnorePattern: "^_",
}],
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-require-imports": "off",
"@typescript-eslint/no-wrapper-object-types": "off",

"prettier/prettier": "warn",
},
Expand Down
2 changes: 1 addition & 1 deletion ios/RNBatch.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#import <React/RCTEventEmitter.h>
#import <Batch/Batch.h>

#define PluginVersion "ReactNative/11.0.0"
#define PluginVersion "ReactNative/11.1.0"

#ifdef RCT_NEW_ARCH_ENABLED
#import <RNBatchSpec/RNBatchSpec.h>
Expand Down
9 changes: 9 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,13 @@ module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['<rootDir>/dist/', '<rootDir>/node_modules/'],
transform: {
'^.+\\.tsx?$': ['ts-jest', {
isolatedModules: true,
tsconfig: {
esModuleInterop: true,
skipLibCheck: true
}
}]
}
};
14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@batch.com/react-native-plugin",
"version": "11.0.0",
"version": "11.1.0",
"description": "Batch.com React-Native Plugin",
"homepage": "https://github.com/BatchLabs/Batch-React-Native-Plugin",
"main": "dist/Batch.js",
Expand Down Expand Up @@ -37,20 +37,22 @@
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.7.0",
"@types/jest": "^27.0.2",
"@types/jest": "^29.5.12",
"@types/react": "^17.0.33",
"@types/react-native": "^0.73.0",
"@typescript-eslint/eslint-plugin": "^7.16.1",
"@typescript-eslint/parser": "^7.16.1",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"eslint": "^9.7.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-simple-import-sort": "^12.1.1",
"expo-module-scripts": "^3.5.2",
"globals": "^15.8.0",
"jest": "^27.3.1",
"jest": "^29.7.0",
"jest-environment-node": "^29.7.0",
"jest-util": "^29.7.0",
"prettier": "^3.3.3",
"react-native": "^0.73.0",
"ts-jest": "^27.0.7",
"ts-jest": "^29.1.0",
"typedoc": "^0.26.4",
"typescript": "^5.5.3"
},
Expand Down
44 changes: 44 additions & 0 deletions plugin/src/__tests__/withReactNativeBatch.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Props } from '../withReactNativeBatch';

describe('withReactNativeBatch', () => {
describe('Props default values', () => {
it('should default shouldUseNonNullableIntent to false when props is undefined', () => {
const props = undefined;
const _props = props || { androidApiKey: '', iosApiKey: '', shouldUseNonNullableIntent: false };

expect(_props.shouldUseNonNullableIntent).toBe(false);
});

it('should default shouldUseNonNullableIntent to false when props is provided without the property', () => {
const props: Props = {
androidApiKey: 'FAKE_ANDROID_API_KEY',
iosApiKey: 'FAKE_IOS_API_KEY',
};
const _props = props || { androidApiKey: '', iosApiKey: '', shouldUseNonNullableIntent: false };

expect(_props.shouldUseNonNullableIntent).toBeUndefined();
});

it('should use provided shouldUseNonNullableIntent value when explicitly set to true', () => {
const props: Props = {
androidApiKey: 'FAKE_ANDROID_API_KEY',
iosApiKey: 'FAKE_IOS_API_KEY',
shouldUseNonNullableIntent: true,
};
const _props = props || { androidApiKey: '', iosApiKey: '', shouldUseNonNullableIntent: false };

expect(_props.shouldUseNonNullableIntent).toBe(true);
});

it('should use provided shouldUseNonNullableIntent value when explicitly set to false', () => {
const props: Props = {
androidApiKey: 'FAKE_ANDROID_API_KEY',
iosApiKey: 'FAKE_IOS_API_KEY',
shouldUseNonNullableIntent: false,
};
const _props = props || { androidApiKey: '', iosApiKey: '', shouldUseNonNullableIntent: false };

expect(_props.shouldUseNonNullableIntent).toBe(false);
});
});
});
25 changes: 17 additions & 8 deletions plugin/src/__tests__/withReactNativeBatchMainActivity.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import { modifyMainActivity } from '../android/withReactNativeBatchMainActivity';
import { modifyMainJavaActivity, modifyMainKotlinActivity } from '../android/withReactNativeBatchMainActivity';
import {
mainJavaActivityExpectedFixture,
mainJavaActivityFixture,
mainKotlinActivityExpectedFixture,
mainKotlinActivityExpectedFixtureNullable,
mainKotlinActivityFixture,
} from '../fixtures/mainActivity';

describe(modifyMainActivity, () => {
it('should push on new intent in java main activity', () => {
const result = modifyMainActivity(mainJavaActivityFixture);
expect(result).toEqual(mainJavaActivityExpectedFixture);
describe('withReactNativeBatchMainActivity', () => {
describe('modifyMainJavaActivity', () => {
it('should push on new intent in java main activity', () => {
const result = modifyMainJavaActivity(mainJavaActivityFixture);
expect(result).toEqual(mainJavaActivityExpectedFixture);
});
});

it('should push on new intent in kotlin main activity', () => {
const result = modifyMainActivity(mainKotlinActivityFixture);
describe('modifyMainKotlinActivity', () => {
it('should push on new intent in kotlin main activity with non-nullable Intent (SDK 54+)', () => {
const result = modifyMainKotlinActivity(mainKotlinActivityFixture, true);
expect(result).toEqual(mainKotlinActivityExpectedFixture);
});

expect(result).toEqual(mainKotlinActivityExpectedFixture);
it('should push on new intent in kotlin main activity with nullable Intent (SDK 53-)', () => {
const result = modifyMainKotlinActivity(mainKotlinActivityFixture, false);
expect(result).toEqual(mainKotlinActivityExpectedFixtureNullable);
});
});
});
29 changes: 22 additions & 7 deletions plugin/src/android/withReactNativeBatchMainActivity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ConfigPlugin, withMainActivity } from '@expo/config-plugins';
import { ConfigPlugin, ExportedConfigWithProps, withMainActivity } from '@expo/config-plugins';
import { ApplicationProjectFile } from '@expo/config-plugins/build/android/Paths';

export type MainActivityProps = {
shouldUseNonNullableIntent?: boolean;
};

export const modifyMainJavaActivity = (content: string): string => {
let newContent = content;
Expand Down Expand Up @@ -44,7 +49,7 @@ import com.batch.android.Batch;`
return newContent;
};

export const modifyMainKotlinActivity = (content: string): string => {
export const modifyMainKotlinActivity = (content: string, useNonNullableIntent: boolean): string => {
let newContent = content;

if (!newContent.includes('import android.content.Intent')) {
Expand All @@ -68,9 +73,12 @@ import com.batch.android.Batch`
const start = newContent.substring(0, lastBracketIndex);
const end = newContent.substring(lastBracketIndex);

// Use non-nullable Intent for SDK 54+, nullable for SDK 53 and below
const intentType = useNonNullableIntent ? 'Intent' : 'Intent?';

newContent =
start +
`\n override fun onNewIntent(intent: Intent?) {
`\n override fun onNewIntent(intent: ${intentType}) {
super.onNewIntent(intent)
Batch.onNewIntent(this, intent)
}\n` +
Expand All @@ -86,21 +94,28 @@ import com.batch.android.Batch`
return newContent;
};

export const modifyMainActivity = (content: string): string => {
return isKotlinMainActivity(content) ? modifyMainKotlinActivity(content) : modifyMainJavaActivity(content);
export const modifyMainActivity = (
config: ExportedConfigWithProps<ApplicationProjectFile>,
shouldUseNonNullableIntent: boolean = false
): string => {
return isKotlinMainActivity(config.modResults.contents)
? modifyMainKotlinActivity(config.modResults.contents, shouldUseNonNullableIntent)
: modifyMainJavaActivity(config.modResults.contents);
};

const isKotlinMainActivity = (content: string): boolean => {
return content.includes('class MainActivity : ReactActivity()');
};

export const withReactNativeBatchMainActivity: ConfigPlugin<object | void> = config => {
export const withReactNativeBatchMainActivity: ConfigPlugin<MainActivityProps | void> = (config, props) => {
const shouldUseNonNullableIntent = props?.shouldUseNonNullableIntent ?? false;

return withMainActivity(config, config => {
return {
...config,
modResults: {
...config.modResults,
contents: modifyMainActivity(config.modResults.contents),
contents: modifyMainActivity(config, shouldUseNonNullableIntent),
},
};
});
Expand Down
70 changes: 70 additions & 0 deletions plugin/src/fixtures/mainActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class MainActivity : ReactActivity() {
}
}`;

// Expected fixture for Expo SDK 54+ (non-nullable Intent)
export const mainKotlinActivityExpectedFixture = `package com.arnaudr.expobeta50

import android.os.Build
Expand All @@ -117,6 +118,75 @@ import com.batch.android.Batch

import expo.modules.ReactActivityDelegateWrapper

class MainActivity : ReactActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Set the theme to AppTheme BEFORE onCreate to support
// coloring the background, status bar, and navigation bar.
// This is required for expo-splash-screen.
setTheme(R.style.AppTheme);
super.onCreate(null)
}

/**
* Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component.
*/
override fun getMainComponentName(): String = "main"

/**
* Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
* which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
*/
override fun createReactActivityDelegate(): ReactActivityDelegate {
return ReactActivityDelegateWrapper(
this,
BuildConfig.IS_NEW_ARCHITECTURE_ENABLED,
object : DefaultReactActivityDelegate(
this,
mainComponentName,
fabricEnabled
){})
}

/**
* Align the back button behavior with Android S
* where moving root activities to background instead of finishing activities.
*/
override fun invokeDefaultOnBackPressed() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
if (!moveTaskToBack(false)) {
// For non-root activities, use the default implementation to finish them.
super.invokeDefaultOnBackPressed()
}
return
}

// Use the default back button implementation on Android S
// because it's doing more than [Activity.moveTaskToBack] in fact.
super.invokeDefaultOnBackPressed()
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Batch.onNewIntent(this, intent)
}
}`;

// Expected fixture for Expo SDK 53 and below (nullable Intent)
export const mainKotlinActivityExpectedFixtureNullable = `package com.arnaudr.expobeta50

import android.os.Build
import android.os.Bundle

import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
import android.content.Intent
import com.batch.android.Batch

import expo.modules.ReactActivityDelegateWrapper

class MainActivity : ReactActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Set the theme to AppTheme BEFORE onCreate to support
Expand Down
8 changes: 7 additions & 1 deletion plugin/src/withReactNativeBatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,26 @@ export type Props = {
enableDefaultOptOut?: boolean;
enableProfileCustomIDMigration?: boolean;
enableProfileCustomDataMigration?: boolean;
shouldUseNonNullableIntent?: boolean;
};
/**
* Apply react-native-batch configuration for Expo SDK 42 projects.
*/
const withReactNativeBatch: ConfigPlugin<Props | void> = (config, props) => {
const _props = props || { androidApiKey: '', iosApiKey: '' };

// Default shouldUseNonNullableIntent to false if not explicitly provided
if (_props.shouldUseNonNullableIntent === undefined) {
_props.shouldUseNonNullableIntent = false;
}

let newConfig = withGoogleServicesFile(config);
newConfig = withClassPath(newConfig);
newConfig = withApplyPlugin(newConfig);
newConfig = withReactNativeBatchManifest(newConfig, _props);
newConfig = withReactNativeBatchAppBuildGradle(newConfig, _props);
newConfig = withReactNativeBatchMainApplication(newConfig);
newConfig = withReactNativeBatchMainActivity(newConfig);
newConfig = withReactNativeBatchMainActivity(newConfig, _props);
newConfig = withReactNativeBatchInfoPlist(newConfig, _props);
newConfig = withReactNativeBatchEntitlements(newConfig);
newConfig = withReactNativeBatchAppDelegate(newConfig);
Expand Down
2 changes: 0 additions & 2 deletions src/NativeRNBatchModule.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/ban-types */

import { TurboModule, TurboModuleRegistry } from 'react-native';

import { BatchInboxFetcher, BatchUserAttribute, IInboxNotification } from './Batch';
Expand Down