Skip to content

Commit 9c3e8f3

Browse files
feat: implement new architecture for Android (#79)
* feat: new arch on android not working * feat: cpp shadow node added * wip * feat: make shadow node work, share the code between ios * fix: albums example on new arch * ci: add new arch job --------- Co-authored-by: sarthak-d11 <sarthak.agarwal@dream11.com> Co-authored-by: sarthak-d11 <111414862+sarthak-d11@users.noreply.github.com>
1 parent 35db931 commit 9c3e8f3

24 files changed

+743
-151
lines changed

.github/workflows/ci.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,62 @@ jobs:
107107
run: |
108108
yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}"
109109
110+
build-android-newarch:
111+
runs-on: ubuntu-latest
112+
env:
113+
TURBO_CACHE_DIR: .turbo/android-newarch
114+
steps:
115+
- name: Checkout
116+
uses: actions/checkout@v3
117+
118+
- name: Setup
119+
uses: ./.github/actions/setup
120+
121+
- name: Cache turborepo for Android new arch
122+
uses: actions/cache@v3
123+
with:
124+
path: ${{ env.TURBO_CACHE_DIR }}
125+
key: ${{ runner.os }}-turborepo-android-newarch-${{ hashFiles('yarn.lock') }}
126+
restore-keys: |
127+
${{ runner.os }}-turborepo-android-newarch-
128+
129+
- name: Check turborepo cache for Android new arch
130+
run: |
131+
TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:android:fabric --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android:fabric').cache.status")
132+
133+
if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then
134+
echo "turbo_cache_hit=1" >> $GITHUB_ENV
135+
fi
136+
137+
- name: Install JDK
138+
if: env.turbo_cache_hit != 1
139+
uses: actions/setup-java@v3
140+
with:
141+
distribution: 'zulu'
142+
java-version: '17'
143+
144+
- name: Finalize Android SDK
145+
if: env.turbo_cache_hit != 1
146+
run: |
147+
/bin/bash -c "yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null"
148+
149+
- name: Cache Gradle
150+
if: env.turbo_cache_hit != 1
151+
uses: actions/cache@v3
152+
with:
153+
path: |
154+
~/.gradle/wrapper
155+
~/.gradle/caches
156+
key: ${{ runner.os }}-gradle-newarch-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }}
157+
restore-keys: |
158+
${{ runner.os }}-gradle-newarch-
159+
160+
- name: Build example for Android new arch
161+
env:
162+
JAVA_OPTS: "-XX:MaxHeapSize=6g"
163+
run: |
164+
yarn turbo run build:android:fabric --cache-dir="${{ env.TURBO_CACHE_DIR }}"
165+
110166
build-ios:
111167
runs-on: macos-15
112168
env:

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ dependencies {
121121
if (isNewArchitectureEnabled()) {
122122
react {
123123
jsRootDir = file("../src/")
124-
libraryName = "RCTTabView"
124+
libraryName = "RNCTabView"
125125
codegenJavaPackageName = "com.rcttabview"
126126
}
127127
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.rcttabview
2+
3+
import android.content.res.ColorStateList
4+
import com.facebook.react.bridge.ReadableArray
5+
import com.facebook.react.common.MapBuilder
6+
7+
data class TabInfo(
8+
val key: String,
9+
val title: String,
10+
val badge: String,
11+
val activeTintColor: Int?
12+
)
13+
14+
class RCTTabViewImpl {
15+
fun getName(): String {
16+
return NAME
17+
}
18+
19+
companion object {
20+
const val NAME = "RNCTabView"
21+
}
22+
23+
fun setItems(view: ReactBottomNavigationView, items: ReadableArray) {
24+
val itemsArray = mutableListOf<TabInfo>()
25+
for (i in 0 until items.size()) {
26+
items.getMap(i).let { item ->
27+
itemsArray.add(
28+
TabInfo(
29+
key = item.getString("key") ?: "",
30+
title = item.getString("title") ?: "",
31+
badge = item.getString("badge") ?: "",
32+
activeTintColor = if (item.hasKey("activeTintColor")) item.getInt("activeTintColor") else null
33+
)
34+
)
35+
}
36+
}
37+
view.updateItems(itemsArray)
38+
}
39+
40+
fun setSelectedPage(view: ReactBottomNavigationView, key: String) {
41+
view.items?.indexOfFirst { it.key == key }?.let {
42+
view.selectedItemId = it
43+
}
44+
}
45+
46+
fun setLabeled(view: ReactBottomNavigationView, flag: Boolean?) {
47+
view.setLabeled(flag)
48+
}
49+
50+
fun setIcons(view: ReactBottomNavigationView, icons: ReadableArray?) {
51+
view.setIcons(icons)
52+
}
53+
54+
fun setBarTintColor(view: ReactBottomNavigationView, color: Int?) {
55+
view.setBarTintColor(color)
56+
}
57+
58+
fun setRippleColor(view: ReactBottomNavigationView, rippleColor: Int?) {
59+
if (rippleColor != null) {
60+
val color = ColorStateList.valueOf(rippleColor)
61+
view.setRippleColor(color)
62+
}
63+
}
64+
65+
fun setActiveTintColor(view: ReactBottomNavigationView, color: Int?) {
66+
view.setActiveTintColor(color)
67+
}
68+
69+
fun setInactiveTintColor(view: ReactBottomNavigationView, color: Int?) {
70+
view.setInactiveTintColor(color)
71+
}
72+
73+
fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any>? {
74+
return MapBuilder.of(
75+
PageSelectedEvent.EVENT_NAME,
76+
MapBuilder.of("registrationName", "onPageSelected"),
77+
TabLongPressEvent.EVENT_NAME,
78+
MapBuilder.of("registrationName", "onTabLongPress")
79+
)
80+
}
81+
}

android/src/main/java/com/rcttabview/RCTTabViewPackage.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import java.util.ArrayList
99
class RCTTabViewPackage : ReactPackage {
1010
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
1111
val viewManagers: MutableList<ViewManager<*, *>> = ArrayList()
12-
viewManagers.add(RCTTabViewViewManager())
12+
viewManagers.add(RCTTabViewManager(reactContext))
1313
return viewManagers
1414
}
1515

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
cmake_minimum_required(VERSION 3.13)
2+
set(CMAKE_VERBOSE_MAKEFILE ON)
3+
4+
set(LIB_LITERAL RNCTabView)
5+
set(LIB_TARGET_NAME react_codegen_${LIB_LITERAL})
6+
7+
set(LIB_ANDROID_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../..)
8+
set(LIB_COMMON_DIR ${LIB_ANDROID_DIR}/../common/cpp)
9+
set(LIB_ANDROID_GENERATED_JNI_DIR ${LIB_ANDROID_DIR}/build/generated/source/codegen/jni)
10+
set(LIB_ANDROID_GENERATED_COMPONENTS_DIR ${LIB_ANDROID_GENERATED_JNI_DIR}/react/renderer/components/${LIB_LITERAL})
11+
12+
add_compile_options(
13+
-fexceptions
14+
-frtti
15+
-std=c++20
16+
-Wall
17+
-Wpedantic
18+
-Wno-gnu-zero-variadic-macro-arguments
19+
)
20+
21+
file(GLOB LIB_CUSTOM_SRCS CONFIGURE_DEPENDS *.cpp ${LIB_COMMON_DIR}/react/renderer/components/${LIB_LITERAL}/*.cpp)
22+
file(GLOB LIB_CODEGEN_SRCS CONFIGURE_DEPENDS ${LIB_ANDROID_GENERATED_JNI_DIR}/*.cpp ${LIB_ANDROID_GENERATED_COMPONENTS_DIR}/*.cpp)
23+
24+
add_library(
25+
${LIB_TARGET_NAME}
26+
SHARED
27+
${LIB_CUSTOM_SRCS}
28+
${LIB_CODEGEN_SRCS}
29+
)
30+
31+
target_include_directories(
32+
${LIB_TARGET_NAME}
33+
PUBLIC
34+
.
35+
${LIB_COMMON_DIR}
36+
${LIB_ANDROID_GENERATED_JNI_DIR}
37+
${LIB_ANDROID_GENERATED_COMPONENTS_DIR}
38+
)
39+
40+
# https://github.com/react-native-community/discussions-and-proposals/discussions/816
41+
# This if-then-else can be removed once this library does not support version below 0.76
42+
if (REACTNATIVE_MERGED_SO)
43+
target_link_libraries(
44+
${LIB_TARGET_NAME}
45+
fbjni
46+
jsi
47+
reactnative
48+
)
49+
else()
50+
target_link_libraries(
51+
${LIB_TARGET_NAME}
52+
fbjni
53+
folly_runtime
54+
glog
55+
jsi
56+
react_codegen_rncore
57+
react_debug
58+
react_render_componentregistry
59+
react_render_core
60+
react_render_debug
61+
react_render_graphics
62+
react_render_imagemanager
63+
react_render_mapbuffer
64+
react_utils
65+
react_nativemodule_core
66+
rrc_image
67+
turbomodulejsijni
68+
rrc_view
69+
yoga
70+
)
71+
endif()
72+
73+
target_compile_options(
74+
${LIB_TARGET_NAME}
75+
PRIVATE
76+
-DLOG_TAG=\"ReactNative\"
77+
-fexceptions
78+
-frtti
79+
-std=c++20
80+
-Wall
81+
)
82+
83+
target_include_directories(
84+
${CMAKE_PROJECT_NAME}
85+
PUBLIC
86+
${CMAKE_CURRENT_SOURCE_DIR}
87+
)

android/src/main/jni/RNCTabView.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#pragma once
2+
3+
#include <ReactCommon/JavaTurboModule.h>
4+
#include <ReactCommon/TurboModule.h>
5+
#include <jsi/jsi.h>
6+
#include <react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h>
7+
8+
namespace facebook::react {
9+
JSI_EXPORT
10+
std::shared_ptr<TurboModule> RNCTabView_ModuleProvider(
11+
const std::string &moduleName,
12+
const JavaTurboModule::InitParams &params);
13+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package com.rcttabview
2+
3+
import android.content.Context
4+
import android.view.View
5+
import com.facebook.react.bridge.ReactApplicationContext
6+
import com.facebook.react.bridge.ReadableArray
7+
import com.facebook.react.bridge.ReadableMap
8+
import com.facebook.react.module.annotations.ReactModule
9+
import com.facebook.react.uimanager.PixelUtil.toDIPFromPixel
10+
import com.facebook.react.uimanager.SimpleViewManager
11+
import com.facebook.react.uimanager.ThemedReactContext
12+
import com.facebook.react.uimanager.UIManagerHelper
13+
import com.facebook.react.uimanager.ViewManagerDelegate
14+
import com.facebook.react.viewmanagers.RNCTabViewManagerDelegate
15+
import com.facebook.react.viewmanagers.RNCTabViewManagerInterface
16+
import com.facebook.yoga.YogaMeasureMode
17+
import com.facebook.yoga.YogaMeasureOutput
18+
19+
20+
@ReactModule(name = RCTTabViewImpl.NAME)
21+
class RCTTabViewManager(context: ReactApplicationContext) :
22+
SimpleViewManager<ReactBottomNavigationView>(),
23+
RNCTabViewManagerInterface<ReactBottomNavigationView> {
24+
25+
private val contextInner: ReactApplicationContext = context
26+
private val delegate: RNCTabViewManagerDelegate<ReactBottomNavigationView, RCTTabViewManager> =
27+
RNCTabViewManagerDelegate(this)
28+
private val tabViewImpl: RCTTabViewImpl = RCTTabViewImpl()
29+
30+
override fun createViewInstance(context: ThemedReactContext): ReactBottomNavigationView {
31+
val view = ReactBottomNavigationView(context)
32+
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
33+
view.onTabSelectedListener = { data ->
34+
data.getString("key")?.let {
35+
eventDispatcher?.dispatchEvent(PageSelectedEvent(viewTag = view.id, key = it))
36+
}
37+
}
38+
39+
view.onTabLongPressedListener = { data ->
40+
data.getString("key")?.let {
41+
eventDispatcher?.dispatchEvent(TabLongPressEvent(viewTag = view.id, key = it))
42+
}
43+
}
44+
return view
45+
46+
}
47+
48+
override fun getName(): String {
49+
return tabViewImpl.getName()
50+
}
51+
52+
override fun setItems(view: ReactBottomNavigationView?, value: ReadableArray?) {
53+
if (view != null && value != null)
54+
tabViewImpl.setItems(view, value)
55+
}
56+
57+
override fun setSelectedPage(view: ReactBottomNavigationView?, value: String?) {
58+
if (view != null && value != null)
59+
tabViewImpl.setSelectedPage(view, value)
60+
}
61+
62+
override fun setIcons(view: ReactBottomNavigationView?, value: ReadableArray?) {
63+
if (view != null)
64+
tabViewImpl.setIcons(view, value)
65+
}
66+
67+
override fun setLabeled(view: ReactBottomNavigationView?, value: Boolean) {
68+
if (view != null)
69+
tabViewImpl.setLabeled(view, value)
70+
}
71+
72+
override fun setRippleColor(view: ReactBottomNavigationView?, value: Int?) {
73+
if (view != null && value != null)
74+
tabViewImpl.setRippleColor(view, value)
75+
}
76+
77+
override fun setBarTintColor(view: ReactBottomNavigationView?, value: Int?) {
78+
if (view != null && value != null)
79+
tabViewImpl.setBarTintColor(view, value)
80+
}
81+
82+
override fun setActiveTintColor(view: ReactBottomNavigationView?, value: Int?) {
83+
if (view != null && value != null)
84+
tabViewImpl.setActiveTintColor(view, value)
85+
}
86+
87+
override fun setInactiveTintColor(view: ReactBottomNavigationView?, value: Int?) {
88+
if (view != null && value != null)
89+
tabViewImpl.setInactiveTintColor(view, value)
90+
}
91+
92+
override fun getDelegate(): ViewManagerDelegate<ReactBottomNavigationView> {
93+
return delegate
94+
}
95+
96+
public override fun measure(
97+
context: Context?,
98+
localData: ReadableMap?,
99+
props: ReadableMap?,
100+
state: ReadableMap?,
101+
width: Float,
102+
widthMode: YogaMeasureMode?,
103+
height: Float,
104+
heightMode: YogaMeasureMode?,
105+
attachmentsPositions: FloatArray?
106+
): Long {
107+
val view = ReactBottomNavigationView(context ?: contextInner)
108+
val measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
109+
view.measure(measureSpec, measureSpec)
110+
111+
return YogaMeasureOutput.make(
112+
toDIPFromPixel(view.measuredWidth.toFloat()),
113+
toDIPFromPixel(view.measuredHeight.toFloat())
114+
)
115+
}
116+
117+
// iOS Methods
118+
119+
override fun setTranslucent(view: ReactBottomNavigationView?, value: Boolean) {
120+
}
121+
122+
override fun setIgnoresTopSafeArea(view: ReactBottomNavigationView?, value: Boolean) {
123+
}
124+
125+
override fun setDisablePageAnimations(view: ReactBottomNavigationView?, value: Boolean) {
126+
}
127+
128+
override fun setSidebarAdaptable(view: ReactBottomNavigationView?, value: Boolean) {
129+
}
130+
131+
override fun setScrollEdgeAppearance(view: ReactBottomNavigationView?, value: String?) {
132+
}
133+
}

0 commit comments

Comments
 (0)