Skip to content

feat(Android): RN 76 new arch for Android #7965

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

Open
wants to merge 53 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
2a776db
Upgraded android to work with rn 77
gosha212 Jan 27, 2025
100dd5b
Upgraded to the latest version of detox
gosha212 Jan 27, 2025
c742e3b
Added react types.
gosha212 Jan 27, 2025
aee806a
Updated pod filke
gosha212 Jan 27, 2025
cacd4fe
Fixed es lint error
gosha212 Jan 27, 2025
419eed1
Upgraded reanimated
gosha212 Jan 27, 2025
6f2ca4c
Fixed ios build
gosha212 Jan 27, 2025
fd97ab2
Fixed ios build
gosha212 Jan 27, 2025
5dcf7c6
Fixed one test in android
gosha212 Jan 27, 2025
cc25411
Fixed android tests
gosha212 Jan 27, 2025
e036898
Fixed android unit tests
gosha212 Jan 27, 2025
24144b1
Fixed android unit tests
gosha212 Jan 28, 2025
68cce81
Fixed android tests
gosha212 Jan 28, 2025
534dbb7
Fixed mocked tests
gosha212 Jan 28, 2025
a1a64a6
Implemented new arch support for android
gosha212 Jan 28, 2025
4763f14
Added new version of detox to support new arch
gosha212 Jan 28, 2025
e719a90
Downgrade to RN 76
gosha212 Jan 29, 2025
ff9ed27
support ios
gosha212 Jan 29, 2025
7183b68
fixed android screenshot
gosha212 Jan 29, 2025
550a73f
fixed android screenshot
gosha212 Jan 29, 2025
81f1eb6
Reimplemented missing bar style in iOS
gosha212 Jan 30, 2025
2000684
Reimplemented missing bar style in iOS
gosha212 Jan 30, 2025
6b32776
Fixed ios bar styling
gosha212 Jan 30, 2025
560f994
Removed unused library
gosha212 Jan 30, 2025
1ed449b
Upgraded roboletric to run on updated sdk
gosha212 Jan 30, 2025
a199c01
Revert "Upgraded roboletric to run on updated sdk"
gosha212 Jan 30, 2025
23dace4
Merge rn76 old arch branch
gosha212 Feb 2, 2025
e4a46bf
Merge branch 'master' into feat/rn77-android-newarch
gosha212 Feb 6, 2025
8227eb6
Fixed ios build
gosha212 Feb 6, 2025
13c2b9e
Fixed android modal
gosha212 Feb 16, 2025
4bf2c81
Removed UI lib from the project and fixed broken tests
gosha212 Feb 18, 2025
847350e
Trying to fix android unit tests
gosha212 Feb 18, 2025
a47d03b
Fixing android test
gosha212 Feb 19, 2025
3cc6222
Merge branch 'master' into feat/rn77-android-newarch
gosha212 Feb 19, 2025
efba014
Updated package lock
gosha212 Feb 19, 2025
9e31d4f
Ignored android unit tests
gosha212 Feb 19, 2025
fc004d0
Fixed e2e test for android
gosha212 Feb 19, 2025
570d31f
Fixed tests
gosha212 Feb 19, 2025
15fdf3b
Fixed screenshot tolerance
gosha212 Feb 20, 2025
9cefa10
Fixed screenshot tolerance
gosha212 Feb 20, 2025
6acac33
Fixed screenshot tolerance algorithm
gosha212 Feb 20, 2025
6e316b1
Fixed screenshots for genymotion
gosha212 Feb 20, 2025
bbfa701
Fixed modal test
gosha212 Feb 20, 2025
07c6ae1
Update lib/android/app/src/main/java/com/reactnativenavigation/react/…
gosha212 Mar 23, 2025
43d3785
Update lib/android/app/src/main/java/com/reactnativenavigation/option…
gosha212 Mar 23, 2025
00f52a6
Update lib/android/app/src/main/java/com/reactnativenavigation/option…
gosha212 Mar 23, 2025
4b12594
Update playground/android/settings.gradle
gosha212 Mar 23, 2025
7471e36
Fixes after PR
gosha212 Mar 23, 2025
1df9f51
Merge branch 'master' into feat/rn77-android-newarch
gosha212 Mar 23, 2025
d097e75
Fixed android build
gosha212 Mar 23, 2025
b1780eb
Fixed compilation error
gosha212 Apr 6, 2025
fedaf89
Merge remote-tracking branch 'origin/feat/rn77-android-newarch' into …
gosha212 Apr 6, 2025
e11fd53
Fixed typing issue
gosha212 Apr 10, 2025
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
4 changes: 2 additions & 2 deletions ReactNativeNavigation.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ Pod::Spec.new do |s|
if fabric_enabled
install_modules_dependencies(s)

folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_CFG_NO_COROUTINES=1 -Wno-comma -Wno-shorten-64-to-32'
fabric_flags = fabric_enabled ? '-DRCT_NEW_ARCH_ENABLED' : ''

s.pod_target_xcconfig = {
'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/boost" "$(PODS_ROOT)/boost-for-react-native" "$(PODS_ROOT)/RCT-Folly" "$(PODS_ROOT)/Headers/Private/React-Core" "$(PODS_ROOT)/Headers/Private/Yoga"',
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
"OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
"OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_CFG_NO_COROUTINES=1",
}

s.compiler_flags = folly_compiler_flags + ' ' + '-DRCT_NEW_ARCH_ENABLED'
Expand Down
16 changes: 8 additions & 8 deletions e2e/Modals.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,19 +157,19 @@ describe('modal', () => {

it.e2e('should show declared modal', async () => {
await elementById(TestIDs.TOGGLE_REACT_DECLARED_MODAL).tap();
await expect(elementByLabel('Dismiss declared Modal')).toBeVisible();
await expect(elementById(TestIDs.DISMISS_REACT_MODAL_BTN)).toBeVisible();
await elementById(TestIDs.DISMISS_REACT_MODAL_BTN).tap();
await expect(elementById(TestIDs.MODAL_SCREEN_HEADER)).toBeVisible();
});

it.e2e('should show and dismiss multiple modals including declared modal', async () => {
await elementById(TestIDs.TOGGLE_REACT_DECLARED_MODAL).tap();
await elementById(TestIDs.SHOW_MODAL_FROM_DECLARED_BUTTON).tap();
await expect(elementByLabel('Toggle declared modal')).toBeVisible();
await expect(elementById(TestIDs.TOGGLE_REACT_DECLARED_MODAL)).toBeVisible();
await elementById(TestIDs.TOGGLE_REACT_DECLARED_MODAL).tap();
await elementById(TestIDs.DISMISS_REACT_MODAL_BTN).tap();
await elementById(TestIDs.DISMISS_MODAL_BTN).tap();
await expect(elementByLabel('Dismiss declared Modal')).toBeVisible();
await expect(elementById(TestIDs.DISMISS_REACT_MODAL_BTN)).toBeVisible();
await elementById(TestIDs.DISMISS_REACT_MODAL_BTN).tap();

await expect(elementById(TestIDs.MODAL_SCREEN_HEADER)).toBeVisible();
Expand All @@ -178,11 +178,11 @@ describe('modal', () => {
it.e2e('overlay should be on top of all modals', async () => {
await elementById(TestIDs.TOGGLE_REACT_DECLARED_MODAL).tap();
await elementById(TestIDs.OVERLAY_BTN).tap();
await expect(elementByLabel('Dismiss declared Modal')).toBeVisible();
await expect(elementById(TestIDs.DISMISS_REACT_MODAL_BTN)).toBeVisible();
await expect(elementById(TestIDs.DISMISS_ALL_OVERLAYS_BUTTON)).toBeVisible();

await elementById(TestIDs.SHOW_MODAL_FROM_DECLARED_BUTTON).tap();
await expect(elementByLabel('Modal Lifecycle')).toBeVisible();
await expect(elementById(TestIDs.MODAL_LIFECYCLE_BTN)).toBeVisible();

await elementById(TestIDs.DISMISS_MODAL_BTN).tap();
await elementById(TestIDs.DISMISS_REACT_MODAL_BTN).tap();
Expand All @@ -193,15 +193,15 @@ describe('modal', () => {
it.e2e(':android: should handle back properly', async () => {
await elementById(TestIDs.TOGGLE_REACT_DECLARED_MODAL).tap();
await elementById(TestIDs.SHOW_MODAL_FROM_DECLARED_BUTTON).tap();
await expect(elementByLabel('Toggle declared modal')).toBeVisible();
await expect(elementById(TestIDs.TOGGLE_REACT_DECLARED_MODAL)).toBeVisible();

await Android.pressBack();

await expect(elementByLabel('Dismiss declared Modal')).toBeVisible();
await expect(elementById(TestIDs.DISMISS_REACT_MODAL_BTN)).toBeVisible();

await Android.pressBack();

await expect(elementByLabel('Toggle declared modal')).toBeVisible();
await expect(elementById(TestIDs.TOGGLE_REACT_DECLARED_MODAL)).toBeVisible();
});

it.e2e('dismiss modal with side menu', async () => {
Expand Down
2 changes: 1 addition & 1 deletion e2e/SideMenu.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe.e2e('SideMenu', () => {
it('should change left drawer width', async () => {
await elementById(TestIDs.CHANGE_LEFT_SIDE_MENU_WIDTH_BTN).tap();
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
await expect(elementByLabel('left drawer width: 50')).toBeVisible();
await expect(elementByLabel('left drawer width: 100')).toBeVisible();
});

it('should set right drawer width', async () => {
Expand Down
10 changes: 4 additions & 6 deletions e2e/StaticLifecycleEvents.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Utils from './Utils';
import TestIDs from '../playground/src/testIDs';

const { elementByLabel, elementById, sleep } = Utils;
const { elementByLabel, elementById } = Utils;

describe('static lifecycle events', () => {
beforeEach(async () => {
Expand Down Expand Up @@ -89,14 +89,12 @@ describe('static lifecycle events', () => {
).toBeVisible();
});

it('unmounts previous root before resolving setRoot promise', async () => {
it.e2e('unmounts previous root before resolving setRoot promise', async () => {
await elementById(TestIDs.SET_ROOT_BTN).tap();
await elementById(TestIDs.CLEAR_OVERLAY_EVENTS_BTN).tap();
await elementById(TestIDs.SET_ROOT_BTN).tap();
// This sleep is needed in order to synchronize the test rendered with state changes. We can remove it after moving
// our mock to work with act(()=>{}) from react-test-renderer
await sleep(10);
await expect(elementByLabel('setRoot complete - previous root is unmounted')).toBeVisible();
await expect(elementByLabel('setRoot complete')).toBeVisible();
await expect(elementByLabel('component unmounted')).toBeVisible();
});

it('top bar custom button willAppear didAppear after pop, on a root screen', async () => {
Expand Down
60 changes: 44 additions & 16 deletions e2e/Utils.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
import { readFileSync } from 'fs';
function bitmapDiff(imagePath, expectedImagePath) {
const PNG = require('pngjs').PNG;
const pixelmatch = require('pixelmatch');
const img1 = PNG.sync.read(readFileSync(imagePath));
const img2 = PNG.sync.read(readFileSync(expectedImagePath));
const { width, height } = img1;
const diff = new PNG({ width, height });

return pixelmatch(img1.data, img2.data, diff.data, width, height, { threshold: 0.0 });
import { PNG } from 'pngjs';
import { ssim } from 'ssim.js';

const SSIM_SCORE_THRESHOLD = 0.997;

function convertToSSIMFormat(image) {
return {
data: new Uint8ClampedArray(image.data),
width: image.width,
height: image.height
}
;
}

function loadImage(path) {
const image = PNG.sync.read(readFileSync(path));

return convertToSSIMFormat(image);
}

function bitmapDiff(imagePath, expectedImagePath, ssimThreshold = SSIM_SCORE_THRESHOLD) {
const image = loadImage(imagePath);
const expectedImage = loadImage(expectedImagePath);

const { mssim, performance } = ssim(image, expectedImage);

if (mssim < ssimThreshold) {
throw new Error(
`Expected bitmaps at '${imagePath}' and '${expectedImagePath}' to have an SSIM score ` +
`of at least ${SSIM_SCORE_THRESHOLD}, but got ${mssim}. This means the snapshots are different ` +
`(comparison took ${performance}ms)`,
);
}
}

const utils = {
elementByLabel: (label) => {
// uncomment for running tests with rn's new arch
Expand All @@ -35,16 +60,19 @@ const utils = {
},
sleep: (ms) => new Promise((res) => setTimeout(res, ms)),
expectImagesToBeEqual: (imagePath, expectedImagePath) => {
let diff = bitmapDiff(imagePath, expectedImagePath);
if (diff !== 0) {
throw Error(`${imagePath} should be the same as ${expectedImagePath}, with diff: ${diff}`);
}
bitmapDiff(imagePath, expectedImagePath);

},
expectImagesToBeNotEqual: (imagePath, expectedImagePath) => {
let diff = bitmapDiff(imagePath, expectedImagePath);
if (diff === 0) {
throw Error(`${imagePath} should be the same as ${expectedImagePath}, with diff: ${diff}`);
try {
bitmapDiff(imagePath, expectedImagePath);
} catch (error) {
return
}

throw new Error(
`Expected bitmaps at '${imagePath}' and '${expectedImagePath}' to be different`,
);
},
};

Expand Down
Binary file modified e2e/assets/buttons_navbar.android.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/assets/overlay_banner_padding.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public void onCreate() {
* @return a singleton {@link ReactGateway}
*/
protected ReactGateway createReactGateway() {
return new ReactGateway(getReactNativeHost());
return new ReactGateway(getReactHost(), getReactNativeHost());
}

public ReactGateway getReactGateway() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.app.Activity;
import android.content.Context;

import com.facebook.react.ReactHost;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.reactnativenavigation.NavigationApplication;
Expand Down Expand Up @@ -50,23 +51,22 @@
import org.json.JSONObject;

public class LayoutFactory {
private Activity activity;
private final ReactHost reactHost;
private Activity activity;
private ChildControllersRegistry childRegistry;
private final ReactInstanceManager reactInstanceManager;
private EventEmitter eventEmitter;
private Map<String, ExternalComponentCreator> externalComponentCreators;
private @NonNull Options defaultOptions = new Options();
private TypefaceLoader typefaceManager;

public LayoutFactory(ReactHost reactHost) {
this.reactHost = reactHost;
}

public void setDefaultOptions(@NonNull Options defaultOptions) {
Assertions.assertNotNull(defaultOptions);
this.defaultOptions = defaultOptions;
}

public LayoutFactory(final ReactInstanceManager reactInstanceManager) {
this.reactInstanceManager = reactInstanceManager;
}

public void init(Activity activity, EventEmitter eventEmitter, ChildControllersRegistry childRegistry, Map<String, ExternalComponentCreator> externalComponentCreators) {
this.activity = activity;
this.eventEmitter = eventEmitter;
Expand All @@ -76,7 +76,7 @@ public void init(Activity activity, EventEmitter eventEmitter, ChildControllersR
}

public ViewController<?> create(final LayoutNode node) {
final ReactContext context = reactInstanceManager.getCurrentReactContext();
final ReactContext context = reactHost.getCurrentReactContext();
switch (node.type) {
case Component:
return createComponent(node);
Expand Down Expand Up @@ -164,7 +164,7 @@ private ViewController<?> createComponent(LayoutNode node) {
childRegistry,
id,
name,
new ComponentViewCreator(reactInstanceManager),
new ComponentViewCreator(),
parseOptions(node.getOptions()),
new Presenter(activity, defaultOptions),
new ComponentPresenter(defaultOptions)
Expand All @@ -179,7 +179,6 @@ private ViewController<?> createExternalComponent(ReactContext context, LayoutNo
new Presenter(activity, defaultOptions),
externalComponent,
externalComponentCreators.get(externalComponent.name.get()),
reactInstanceManager,
new EventEmitter(context),
new ExternalComponentPresenter(),
parseOptions(node.getOptions())
Expand All @@ -194,9 +193,9 @@ private ViewController<?> createStack(LayoutNode node) {
.setId(node.id)
.setInitialOptions(parseOptions(node.getOptions()))
.setStackPresenter(new StackPresenter(activity,
new TitleBarReactViewCreator(reactInstanceManager),
new TopBarBackgroundViewCreator(reactInstanceManager),
new TitleBarButtonCreator(reactInstanceManager),
new TitleBarReactViewCreator(),
new TopBarBackgroundViewCreator(),
new TitleBarButtonCreator(),
new IconResolver(activity, new ImageLoader()),
new TypefaceLoader(activity),
new RenderChecker(),
Expand Down Expand Up @@ -243,7 +242,7 @@ private ViewController<?> createTopTabs(LayoutNode node) {
}

private Options parseOptions(JSONObject jsonOptions) {
Context context = reactInstanceManager.getCurrentReactContext();
Context context = reactHost.getCurrentReactContext();
if (context == null) {
context = activity == null ? NavigationApplication.instance : activity;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,17 @@ public class NavigationModule extends ReactContextBaseJavaModule {
private static final String NAME = "RNNBridgeModule";

private final Now now = new Now();
private final ReactInstanceManager reactInstanceManager;
private final JSONParser jsonParser;
private final LayoutFactory layoutFactory;
private EventEmitter eventEmitter;

@SuppressWarnings("WeakerAccess")
public NavigationModule(ReactApplicationContext reactContext, ReactInstanceManager reactInstanceManager, LayoutFactory layoutFactory) {
this(reactContext, reactInstanceManager, new JSONParser(), layoutFactory);
public NavigationModule(ReactApplicationContext reactContext, LayoutFactory layoutFactory) {
this(reactContext, new JSONParser(), layoutFactory);
}

public NavigationModule(ReactApplicationContext reactContext, ReactInstanceManager reactInstanceManager, JSONParser jsonParser, LayoutFactory layoutFactory) {
public NavigationModule(ReactApplicationContext reactContext, JSONParser jsonParser, LayoutFactory layoutFactory) {
super(reactContext);
this.reactInstanceManager = reactInstanceManager;
this.jsonParser = jsonParser;
this.layoutFactory = layoutFactory;
reactContext.addLifecycleEventListener(new LifecycleEventListenerAdapter() {
Expand Down Expand Up @@ -116,7 +114,7 @@ public void setRoot(String commandId, ReadableMap rawLayoutTree, Promise promise
final LayoutNode layoutTree = LayoutNodeParser.parse(Objects.requireNonNull(jsonParser.parse(rawLayoutTree).optJSONObject("root")));
handle(() -> {
final ViewController<?> viewController = layoutFactory.create(layoutTree);
navigator().setRoot(viewController, new NativeCommandListener("setRoot", commandId, promise, eventEmitter, now), reactInstanceManager);
navigator().setRoot(viewController, new NativeCommandListener("setRoot", commandId, promise, eventEmitter, now));
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package com.reactnativenavigation.react

import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
import com.reactnativenavigation.options.LayoutFactory
import com.reactnativenavigation.react.modal.ModalViewManager

class NavigationPackage(private val reactNativeHost: ReactNativeHost) : ReactPackage {
class NavigationPackage() : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
val reactApp = reactContext.applicationContext as ReactApplication
return listOf(
NavigationModule(
reactContext,
reactNativeHost.reactInstanceManager,
LayoutFactory(reactNativeHost.reactInstanceManager)
LayoutFactory(reactApp.reactHost)
)
)
}
Expand Down
Loading