Skip to content

Commit b9c9b72

Browse files
WoLewickija1ns
authored andcommitted
feat: refactor snapshots when going back on Fabric (software-mansion#2134)
PR adding snapshots when going back on Fabric on Android and changing the behavior a bit on iOS.
1 parent 7dfef7a commit b9c9b72

20 files changed

+341
-50
lines changed

RNScreens.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
44

55
new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
66
platform = new_arch_enabled ? "11.0" : "9.0"
7-
source_files = new_arch_enabled ? 'ios/**/*.{h,m,mm,cpp}' : ["ios/**/*.{h,m,mm}", "cpp/**/*.{cpp,h}"]
7+
source_files = new_arch_enabled ? 'ios/**/*.{h,m,mm,cpp}' : ["ios/**/*.{h,m,mm}", "cpp/RNScreensTurboModule.cpp", "cpp/RNScreensTurboModule.h"]
88

99
Pod::Spec.new do |s|
1010
s.name = "RNScreens"

android/CMakeLists.txt

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,22 @@ cmake_minimum_required(VERSION 3.9.0)
22

33
project(rnscreens)
44

5+
if(${RNS_NEW_ARCH_ENABLED})
56
add_library(rnscreens
67
SHARED
78
../cpp/RNScreensTurboModule.cpp
9+
../cpp/RNSScreenRemovalListener.cpp
810
./src/main/cpp/jni-adapter.cpp
11+
./src/main/cpp/NativeProxy.cpp
12+
./src/main/cpp/OnLoad.cpp
913
)
14+
else()
15+
add_library(rnscreens
16+
SHARED
17+
../cpp/RNScreensTurboModule.cpp
18+
./src/main/cpp/jni-adapter.cpp
19+
)
20+
endif()
1021

1122
include_directories(
1223
../cpp
@@ -19,9 +30,42 @@ set_target_properties(rnscreens PROPERTIES
1930
POSITION_INDEPENDENT_CODE ON
2031
)
2132

33+
target_compile_definitions(
34+
rnscreens
35+
PRIVATE
36+
-DFOLLY_NO_CONFIG=1
37+
)
38+
2239
find_package(ReactAndroid REQUIRED CONFIG)
2340

24-
target_link_libraries(rnscreens
25-
ReactAndroid::jsi
26-
android
27-
)
41+
if(${RNS_NEW_ARCH_ENABLED})
42+
find_package(fbjni REQUIRED CONFIG)
43+
44+
target_link_libraries(
45+
rnscreens
46+
ReactAndroid::jsi
47+
ReactAndroid::react_nativemodule_core
48+
ReactAndroid::react_utils
49+
ReactAndroid::reactnativejni
50+
ReactAndroid::fabricjni
51+
ReactAndroid::react_debug
52+
ReactAndroid::react_render_core
53+
ReactAndroid::runtimeexecutor
54+
ReactAndroid::react_render_graphics
55+
ReactAndroid::rrc_view
56+
ReactAndroid::yoga
57+
ReactAndroid::rrc_text
58+
ReactAndroid::glog
59+
ReactAndroid::react_render_componentregistry
60+
ReactAndroid::react_render_consistency
61+
ReactAndroid::react_performance_timeline
62+
ReactAndroid::react_render_observers_events
63+
fbjni::fbjni
64+
android
65+
)
66+
else()
67+
target_link_libraries(rnscreens
68+
ReactAndroid::jsi
69+
android
70+
)
71+
endif()

android/build.gradle

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def reactProperties = new Properties()
8181
file("$reactNativeRootDir/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) }
8282
def REACT_NATIVE_VERSION = reactProperties.getProperty("VERSION_NAME")
8383
def REACT_NATIVE_MINOR_VERSION = REACT_NATIVE_VERSION.startsWith("0.0.0-") ? 1000 : REACT_NATIVE_VERSION.split("\\.")[1].toInteger()
84+
def IS_NEW_ARCHITECTURE_ENABLED = isNewArchitectureEnabled()
8485

8586
android {
8687
compileSdkVersion safeExtGet('compileSdkVersion', rnsDefaultCompileSdkVersion)
@@ -106,13 +107,14 @@ android {
106107
targetSdkVersion safeExtGet('targetSdkVersion', rnsDefaultTargetSdkVersion)
107108
versionCode 1
108109
versionName "1.0"
109-
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
110+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", IS_NEW_ARCHITECTURE_ENABLED.toString()
110111
ndk {
111112
abiFilters (*reactNativeArchitectures())
112113
}
113114
externalNativeBuild {
114115
cmake {
115-
arguments "-DANDROID_STL=c++_shared"
116+
arguments "-DANDROID_STL=c++_shared",
117+
"-DRNS_NEW_ARCH_ENABLED=${IS_NEW_ARCHITECTURE_ENABLED}"
116118
}
117119
}
118120
}
@@ -143,13 +145,15 @@ android {
143145
"META-INF/**",
144146
"**/libjsi.so",
145147
"**/libc++_shared.so",
146-
"**/libreact_render*.so"
148+
"**/libreact_render*.so",
149+
"**/libreactnativejni.so",
150+
"**/libreact_performance_timeline.so"
147151
]
148152
}
149153
sourceSets.main {
150154
ext.androidResDir = "src/main/res"
151155
java {
152-
if (isNewArchitectureEnabled()) {
156+
if (IS_NEW_ARCHITECTURE_ENABLED) {
153157
srcDirs += [
154158
"src/fabric/java",
155159
]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.swmansion.rnscreens
2+
3+
import android.util.Log
4+
import com.facebook.jni.HybridData
5+
import com.facebook.proguard.annotations.DoNotStrip
6+
import com.facebook.react.fabric.FabricUIManager
7+
import java.lang.ref.WeakReference
8+
import java.util.concurrent.ConcurrentHashMap
9+
10+
class NativeProxy {
11+
@DoNotStrip
12+
@Suppress("unused")
13+
private val mHybridData: HybridData
14+
15+
init {
16+
mHybridData = initHybrid()
17+
}
18+
19+
private external fun initHybrid(): HybridData
20+
21+
external fun nativeAddMutationsListener(fabricUIManager: FabricUIManager)
22+
23+
companion object {
24+
// we use ConcurrentHashMap here since it will be read on the JS thread,
25+
// and written to on the UI thread.
26+
private val viewsMap = ConcurrentHashMap<Int, WeakReference<Screen>>()
27+
28+
fun addScreenToMap(
29+
tag: Int,
30+
view: Screen,
31+
) {
32+
viewsMap[tag] = WeakReference(view)
33+
}
34+
35+
fun removeScreenFromMap(tag: Int) {
36+
viewsMap.remove(tag)
37+
}
38+
39+
fun clearMapOnInvalidate() {
40+
viewsMap.clear()
41+
}
42+
}
43+
44+
@DoNotStrip
45+
public fun notifyScreenRemoved(screenTag: Int) {
46+
val screen = viewsMap[screenTag]?.get()
47+
if (screen is Screen) {
48+
screen.startRemovalTransition()
49+
} else {
50+
Log.w("[RNScreens]", "Did not find view with tag $screenTag.")
51+
}
52+
}
53+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#include <fbjni/fbjni.h>
2+
#include <react/fabric/Binding.h>
3+
#include <react/renderer/scheduler/Scheduler.h>
4+
5+
#include <string>
6+
7+
#include "NativeProxy.h"
8+
9+
using namespace facebook;
10+
using namespace react;
11+
12+
namespace rnscreens {
13+
14+
NativeProxy::NativeProxy(jni::alias_ref<NativeProxy::javaobject> jThis)
15+
: javaPart_(jni::make_global(jThis)) {}
16+
17+
NativeProxy::~NativeProxy() {}
18+
19+
void NativeProxy::registerNatives() {
20+
registerHybrid(
21+
{makeNativeMethod("initHybrid", NativeProxy::initHybrid),
22+
makeNativeMethod(
23+
"nativeAddMutationsListener",
24+
NativeProxy::nativeAddMutationsListener)});
25+
}
26+
27+
void NativeProxy::nativeAddMutationsListener(
28+
jni::alias_ref<facebook::react::JFabricUIManager::javaobject>
29+
fabricUIManager) {
30+
auto uiManager =
31+
fabricUIManager->getBinding()->getScheduler()->getUIManager();
32+
screenRemovalListener_ =
33+
std::make_shared<RNSScreenRemovalListener>([this](int tag) {
34+
static const auto method =
35+
javaPart_->getClass()->getMethod<void(jint)>("notifyScreenRemoved");
36+
method(javaPart_, tag);
37+
});
38+
39+
uiManager->getShadowTreeRegistry().enumerate(
40+
[this](const facebook::react::ShadowTree &shadowTree, bool &stop) {
41+
shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
42+
screenRemovalListener_);
43+
});
44+
}
45+
46+
jni::local_ref<NativeProxy::jhybriddata> NativeProxy::initHybrid(
47+
jni::alias_ref<jhybridobject> jThis) {
48+
return makeCxxInstance(jThis);
49+
}
50+
51+
} // namespace rnscreens

android/src/main/cpp/NativeProxy.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#pragma once
2+
3+
#include <fbjni/fbjni.h>
4+
#include <react/fabric/JFabricUIManager.h>
5+
#include "RNSScreenRemovalListener.h"
6+
7+
#include <string>
8+
9+
namespace rnscreens {
10+
using namespace facebook;
11+
using namespace facebook::jni;
12+
13+
class NativeProxy : public jni::HybridClass<NativeProxy> {
14+
public:
15+
std::shared_ptr<RNSScreenRemovalListener> screenRemovalListener_;
16+
static auto constexpr kJavaDescriptor =
17+
"Lcom/swmansion/rnscreens/NativeProxy;";
18+
static jni::local_ref<jhybriddata> initHybrid(
19+
jni::alias_ref<jhybridobject> jThis);
20+
static void registerNatives();
21+
22+
~NativeProxy();
23+
24+
private:
25+
friend HybridBase;
26+
jni::global_ref<NativeProxy::javaobject> javaPart_;
27+
28+
explicit NativeProxy(jni::alias_ref<NativeProxy::javaobject> jThis);
29+
30+
void nativeAddMutationsListener(
31+
jni::alias_ref<facebook::react::JFabricUIManager::javaobject>
32+
fabricUIManager);
33+
};
34+
35+
} // namespace rnscreens

android/src/main/cpp/OnLoad.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include <fbjni/fbjni.h>
2+
3+
#include "NativeProxy.h"
4+
5+
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
6+
return facebook::jni::initialize(
7+
vm, [] { rnscreens::NativeProxy::registerNatives(); });
8+
}

android/src/main/java/com/swmansion/rnscreens/Screen.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.graphics.Paint
66
import android.os.Parcelable
77
import android.util.SparseArray
88
import android.util.TypedValue
9+
import android.view.View
910
import android.view.ViewGroup
1011
import android.view.WindowManager
1112
import android.webkit.WebView
@@ -37,6 +38,7 @@ class Screen(
3738
var screenOrientation: Int? = null
3839
private set
3940
var isStatusBarAnimated: Boolean? = null
41+
var isBeingRemoved = false
4042

4143
init {
4244
// we set layout params as WindowManager.LayoutParams to workaround the issue with TextInputs
@@ -280,6 +282,40 @@ class Screen(
280282

281283
var nativeBackButtonDismissalEnabled: Boolean = true
282284

285+
fun startRemovalTransition() {
286+
if (!isBeingRemoved) {
287+
isBeingRemoved = true
288+
startTransitionRecursive(this)
289+
}
290+
}
291+
292+
private fun startTransitionRecursive(parent: ViewGroup?) {
293+
parent?.let {
294+
for (i in 0 until it.childCount) {
295+
val child = it.getChildAt(i)
296+
if (child.javaClass.simpleName.equals("CircleImageView")) {
297+
// SwipeRefreshLayout class which has CircleImageView as a child,
298+
// does not handle `startViewTransition` properly.
299+
// It has a custom `getChildDrawingOrder` method which returns
300+
// wrong index if we called `startViewTransition` on the views on new arch.
301+
// We add a simple View to bump the number of children to make it work.
302+
// TODO: find a better way to handle this scenario
303+
it.addView(View(context), i)
304+
} else {
305+
child?.let { view -> it.startViewTransition(view) }
306+
}
307+
if (child is ScreenStackHeaderConfig) {
308+
// we want to start transition on children of the toolbar too,
309+
// which is not a child of ScreenStackHeaderConfig
310+
startTransitionRecursive(child.toolbar)
311+
}
312+
if (child is ViewGroup) {
313+
startTransitionRecursive(child)
314+
}
315+
}
316+
}
317+
}
318+
283319
private fun calculateHeaderHeight(): Pair<Double, Double> {
284320
val actionBarTv = TypedValue()
285321
val resolvedActionBarSize =

android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ class ScreenStackHeaderConfig(
304304
}
305305

306306
private fun maybeUpdate() {
307-
if (parent != null && !isDestroyed) {
307+
if (parent != null && !isDestroyed && screen?.isBeingRemoved == false) {
308308
onUpdate()
309309
}
310310
}

android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ class ScreenStackHeaderConfigViewManager :
203203

204204
// TODO: Find better way to handle platform specific props
205205
private fun logNotAvailable(propName: String) {
206-
Log.w("RN SCREENS", "$propName prop is not available on Android")
206+
Log.w("[RNScreens]", "$propName prop is not available on Android")
207207
}
208208

209209
override fun setBackTitle(

0 commit comments

Comments
 (0)