nitrogen 0.36.5 → 0.37.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/syntax/createType.js +13 -10
- package/lib/syntax/kotlin/KotlinCxxBridgedType.js +7 -0
- package/lib/views/CppHybridViewComponent.js +39 -111
- package/lib/views/kotlin/KotlinHybridViewManager.js +72 -33
- package/lib/views/swift/SwiftHybridViewManager.js +45 -23
- package/package.json +2 -2
- package/src/syntax/createType.ts +26 -10
- package/src/syntax/kotlin/KotlinCxxBridgedType.ts +7 -0
- package/src/views/CppHybridViewComponent.ts +41 -116
- package/src/views/kotlin/KotlinHybridViewManager.ts +72 -33
- package/src/views/swift/SwiftHybridViewManager.ts +50 -25
package/lib/syntax/createType.js
CHANGED
|
@@ -45,6 +45,15 @@ function getFunctionCallSignature(func) {
|
|
|
45
45
|
}
|
|
46
46
|
return callSignature;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Whether the given type is optional (`T | undefined`) or not.
|
|
50
|
+
*
|
|
51
|
+
* Note that `null` is a separate type in Nitro (`NullType`) - only `undefined`
|
|
52
|
+
* (or a `?`) makes a type optional.
|
|
53
|
+
*/
|
|
54
|
+
function isOptionalType(type) {
|
|
55
|
+
return type.getUnionTypes().some((t) => t.isUndefined());
|
|
56
|
+
}
|
|
48
57
|
function removeDuplicates(types) {
|
|
49
58
|
return types.filter((t1, index, array) => {
|
|
50
59
|
const firstIndexOfType = array.findIndex((t2) => t1.getCode('c++') === t2.getCode('c++'));
|
|
@@ -185,7 +194,7 @@ export function createType(language, type, isOptional, typeNode) {
|
|
|
185
194
|
}
|
|
186
195
|
else if (type.isArray()) {
|
|
187
196
|
const arrayElementType = type.getArrayElementTypeOrThrow();
|
|
188
|
-
const elementType = createType(language, arrayElementType,
|
|
197
|
+
const elementType = createType(language, arrayElementType, isOptionalType(arrayElementType));
|
|
189
198
|
return new ArrayType(elementType);
|
|
190
199
|
}
|
|
191
200
|
else if (type.isTuple()) {
|
|
@@ -198,10 +207,7 @@ export function createType(language, type, isOptional, typeNode) {
|
|
|
198
207
|
// It's a function!
|
|
199
208
|
const callSignature = getFunctionCallSignature(type);
|
|
200
209
|
const funcReturnType = callSignature.getReturnType();
|
|
201
|
-
const
|
|
202
|
-
.getUnionTypes()
|
|
203
|
-
.some((t) => t.isUndefined());
|
|
204
|
-
const returnType = createType(language, funcReturnType, isReturnOptional);
|
|
210
|
+
const returnType = createType(language, funcReturnType, isOptionalType(funcReturnType));
|
|
205
211
|
const parameters = callSignature.getParameters().map((p) => {
|
|
206
212
|
const declaration = p.getValueDeclarationOrThrow();
|
|
207
213
|
const parameterType = p.getTypeAtLocation(declaration);
|
|
@@ -214,17 +220,14 @@ export function createType(language, type, isOptional, typeNode) {
|
|
|
214
220
|
else if (isPromise(type)) {
|
|
215
221
|
// It's a Promise!
|
|
216
222
|
const [promiseResolvingType] = getArguments(type, 'Promise', 1);
|
|
217
|
-
const
|
|
218
|
-
.getUnionTypes()
|
|
219
|
-
.some((t) => t.isUndefined());
|
|
220
|
-
const resolvingType = createType(language, promiseResolvingType, isResolvingOptional);
|
|
223
|
+
const resolvingType = createType(language, promiseResolvingType, isOptionalType(promiseResolvingType));
|
|
221
224
|
return new PromiseType(resolvingType);
|
|
222
225
|
}
|
|
223
226
|
else if (isRecord(type)) {
|
|
224
227
|
// Record<K, V> -> unordered_map<K, V>
|
|
225
228
|
const [keyTypeT, valueTypeT] = getArguments(type, 'Record', 2);
|
|
226
229
|
const keyType = createType(language, keyTypeT, false);
|
|
227
|
-
const valueType = createType(language, valueTypeT,
|
|
230
|
+
const valueType = createType(language, valueTypeT, isOptionalType(valueTypeT));
|
|
228
231
|
return new RecordType(keyType, valueType);
|
|
229
232
|
}
|
|
230
233
|
else if (isArrayBuffer(type)) {
|
|
@@ -440,6 +440,13 @@ export class KotlinCxxBridgedType {
|
|
|
440
440
|
}
|
|
441
441
|
dereferenceToJObject(parameterName) {
|
|
442
442
|
switch (this.type.kind) {
|
|
443
|
+
// an optional bridges to a (possibly `nullptr`) ref of its wrapping type,
|
|
444
|
+
// so it needs to be dereferenced the same way as the type it wraps.
|
|
445
|
+
case 'optional': {
|
|
446
|
+
const optional = getTypeAs(this.type, OptionalType);
|
|
447
|
+
const bridge = new KotlinCxxBridgedType(optional.wrappingType);
|
|
448
|
+
return bridge.dereferenceToJObject(parameterName);
|
|
449
|
+
}
|
|
443
450
|
// any jni::HybridClass needs to be dereferenced to jobject with .get()
|
|
444
451
|
case 'array-buffer':
|
|
445
452
|
case 'function':
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createIndentation, indent } from '../utils.js';
|
|
2
|
-
import { createFileMetadataString, escapeCppName,
|
|
2
|
+
import { createFileMetadataString, escapeCppName, isNotDuplicate, } from '../syntax/helpers.js';
|
|
3
3
|
import { getHybridObjectName } from '../syntax/getHybridObjectName.js';
|
|
4
4
|
import { includeHeader } from '../syntax/c++/includeNitroHeader.js';
|
|
5
5
|
import { createHostComponentJs } from './createHostComponentJs.js';
|
|
@@ -36,8 +36,16 @@ export function createViewComponentShadowNodeFiles(spec) {
|
|
|
36
36
|
const { propsClassName, stateClassName, nameVariable, shadowNodeClassName, descriptorClassName, component, } = getViewComponentNames(spec);
|
|
37
37
|
const namespace = spec.config.getCxxNamespace('c++', 'views');
|
|
38
38
|
const props = [...spec.properties, getHybridRefProperty(spec)];
|
|
39
|
-
const properties = props.map((p) => `CachedProp<${p.type.getCode('c++')}> ${escapeCppName(p.name)};`);
|
|
40
|
-
const
|
|
39
|
+
const properties = props.map((p) => `nitro::CachedProp<${p.type.getCode('c++')}> ${escapeCppName(p.name)};`);
|
|
40
|
+
const filterCases = props.map((prop) => `case hashString("${prop.name}"): return true;`);
|
|
41
|
+
const comparisons = props.map((prop) => {
|
|
42
|
+
const name = escapeCppName(prop.name);
|
|
43
|
+
return `${name}.hasSameValue(other.${name})`;
|
|
44
|
+
});
|
|
45
|
+
const providedChecks = props.map((prop) => {
|
|
46
|
+
const name = escapeCppName(prop.name);
|
|
47
|
+
return `${name}.isProvided()`;
|
|
48
|
+
});
|
|
41
49
|
const includes = props
|
|
42
50
|
.flatMap((p) => p.getRequiredImports('c++').map((i) => includeHeader(i, true)))
|
|
43
51
|
.filter(isNotDuplicate);
|
|
@@ -48,14 +56,15 @@ ${createFileMetadataString(`${component}.hpp`)}
|
|
|
48
56
|
|
|
49
57
|
#pragma once
|
|
50
58
|
|
|
51
|
-
#include <optional>
|
|
52
|
-
#include <NitroModules/NitroDefines.hpp>
|
|
53
|
-
#include <NitroModules/NitroHash.hpp>
|
|
54
59
|
#include <NitroModules/CachedProp.hpp>
|
|
55
|
-
#include <
|
|
56
|
-
#include <
|
|
60
|
+
#include <NitroModules/ViewComponentDescriptor.hpp>
|
|
61
|
+
#include <NitroModules/ViewPropsHolderState.hpp>
|
|
57
62
|
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
|
58
63
|
#include <react/renderer/components/view/ViewProps.h>
|
|
64
|
+
#include <react/renderer/core/PropsParserContext.h>
|
|
65
|
+
#include <react/renderer/core/RawProps.h>
|
|
66
|
+
|
|
67
|
+
#include <string>
|
|
59
68
|
|
|
60
69
|
${includes.join('\n')}
|
|
61
70
|
|
|
@@ -81,6 +90,16 @@ namespace ${namespace} {
|
|
|
81
90
|
public:
|
|
82
91
|
${indent(properties.join('\n'), ' ')}
|
|
83
92
|
|
|
93
|
+
[[nodiscard]]
|
|
94
|
+
bool hasSameProps(const ${propsClassName}& other) const noexcept {
|
|
95
|
+
return ${comparisons.join(' &&\n ')};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
[[nodiscard]]
|
|
99
|
+
bool hasAnyProvidedProps() const noexcept {
|
|
100
|
+
return ${providedChecks.join(' ||\n ')};
|
|
101
|
+
}
|
|
102
|
+
|
|
84
103
|
private:
|
|
85
104
|
static bool filterObjectKeys(const std::string& propName);
|
|
86
105
|
};
|
|
@@ -88,32 +107,7 @@ namespace ${namespace} {
|
|
|
88
107
|
/**
|
|
89
108
|
* State for the "${spec.name}" View.
|
|
90
109
|
*/
|
|
91
|
-
|
|
92
|
-
public:
|
|
93
|
-
${stateClassName}() = default;
|
|
94
|
-
explicit ${stateClassName}(const std::shared_ptr<${propsClassName}>& props):
|
|
95
|
-
_props(props) {}
|
|
96
|
-
|
|
97
|
-
public:
|
|
98
|
-
[[nodiscard]]
|
|
99
|
-
const std::shared_ptr<${propsClassName}>& getProps() const {
|
|
100
|
-
return _props;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
public:
|
|
104
|
-
#ifdef ANDROID
|
|
105
|
-
${stateClassName}(const ${stateClassName}& /* previousState */, folly::dynamic /* data */) {}
|
|
106
|
-
folly::dynamic getDynamic() const {
|
|
107
|
-
throw std::runtime_error("${stateClassName} does not support folly!");
|
|
108
|
-
}
|
|
109
|
-
react::MapBuffer getMapBuffer() const {
|
|
110
|
-
throw std::runtime_error("${stateClassName} does not support MapBuffer!");
|
|
111
|
-
};
|
|
112
|
-
#endif
|
|
113
|
-
|
|
114
|
-
private:
|
|
115
|
-
std::shared_ptr<${propsClassName}> _props;
|
|
116
|
-
};
|
|
110
|
+
using ${stateClassName} = nitro::ViewPropsHolderState<${propsClassName}>;
|
|
117
111
|
|
|
118
112
|
/**
|
|
119
113
|
* The Shadow Node for the "${spec.name}" View.
|
|
@@ -126,74 +120,33 @@ namespace ${namespace} {
|
|
|
126
120
|
/**
|
|
127
121
|
* The Component Descriptor for the "${spec.name}" View.
|
|
128
122
|
*/
|
|
129
|
-
|
|
130
|
-
public:
|
|
131
|
-
explicit ${descriptorClassName}(const react::ComponentDescriptorParameters& parameters);
|
|
132
|
-
|
|
133
|
-
public:
|
|
134
|
-
/**
|
|
135
|
-
* A faster path for cloning props - reuses the caching logic from \`${propsClassName}\`.
|
|
136
|
-
*/
|
|
137
|
-
std::shared_ptr<const react::Props> cloneProps(const react::PropsParserContext& context,
|
|
138
|
-
const std::shared_ptr<const react::Props>& props,
|
|
139
|
-
react::RawProps rawProps) const override;
|
|
140
|
-
#ifdef ANDROID
|
|
141
|
-
void adopt(react::ShadowNode& shadowNode) const override;
|
|
142
|
-
#endif
|
|
143
|
-
};
|
|
123
|
+
using ${descriptorClassName} = nitro::ViewComponentDescriptor<${shadowNodeClassName}>;
|
|
144
124
|
|
|
145
125
|
/* The actual view for "${spec.name}" needs to be implemented in platform-specific code. */
|
|
146
126
|
|
|
147
127
|
} // namespace ${namespace}
|
|
148
128
|
`.trim();
|
|
149
|
-
// .cpp code
|
|
150
129
|
const propInitializers = [
|
|
151
130
|
'react::ViewProps(context, sourceProps, rawProps, filterObjectKeys)',
|
|
131
|
+
...props.map((prop) => {
|
|
132
|
+
const name = escapeCppName(prop.name);
|
|
133
|
+
const type = prop.type.getCode('c++');
|
|
134
|
+
return `${name}(nitro::CachedProp<${type}>::fromRawValue("${spec.name}", "${prop.name}", rawProps, sourceProps.${name}))`;
|
|
135
|
+
}),
|
|
152
136
|
];
|
|
153
|
-
const propCopyInitializers = ['react::ViewProps()'];
|
|
154
|
-
for (const prop of props) {
|
|
155
|
-
const name = escapeCppName(prop.name);
|
|
156
|
-
const type = prop.type.getCode('c++');
|
|
157
|
-
let valueConversion = `value`;
|
|
158
|
-
if (isFunction(prop.type)) {
|
|
159
|
-
// Due to a React limitation, functions cannot be passed to native directly,
|
|
160
|
-
// because RN converts them to booleans (`true`). Nitro knows this and just
|
|
161
|
-
// wraps functions as objects - the original function is stored in `f`.
|
|
162
|
-
valueConversion = `value.asObject(*runtime).getProperty(*runtime, PropNameIDCache::get(*runtime, "f"))`;
|
|
163
|
-
}
|
|
164
|
-
propInitializers.push(`
|
|
165
|
-
${name}([&]() -> CachedProp<${type}> {
|
|
166
|
-
try {
|
|
167
|
-
const react::RawValue* rawValue = rawProps.at("${prop.name}", nullptr, nullptr);
|
|
168
|
-
if (rawValue == nullptr) return sourceProps.${name};
|
|
169
|
-
const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
|
|
170
|
-
return CachedProp<${type}>::fromRawValue(*runtime, ${valueConversion}, sourceProps.${name});
|
|
171
|
-
} catch (const std::exception& exc) {
|
|
172
|
-
throw std::runtime_error(std::string("${spec.name}.${prop.name}: ") + exc.what());
|
|
173
|
-
}
|
|
174
|
-
}())`.trim());
|
|
175
|
-
propCopyInitializers.push(`${name}(other.${name})`);
|
|
176
|
-
}
|
|
177
137
|
const ctorIndent = createIndentation(propsClassName.length * 2);
|
|
178
|
-
const descriptorIndent = createIndentation(descriptorClassName.length);
|
|
179
138
|
const componentCode = `
|
|
180
139
|
${createFileMetadataString(`${component}.cpp`)}
|
|
181
140
|
|
|
182
141
|
#include "${component}.hpp"
|
|
183
142
|
|
|
184
|
-
#include <
|
|
185
|
-
#include <
|
|
186
|
-
#include <utility>
|
|
187
|
-
#include <NitroModules/NitroDefines.hpp>
|
|
188
|
-
#include <NitroModules/JSIConverter.hpp>
|
|
189
|
-
#include <NitroModules/PropNameIDCache.hpp>
|
|
190
|
-
#include <react/renderer/core/RawValue.h>
|
|
191
|
-
#include <react/renderer/core/ShadowNode.h>
|
|
192
|
-
#include <react/renderer/core/ComponentDescriptor.h>
|
|
193
|
-
#include <react/renderer/components/view/ViewProps.h>
|
|
143
|
+
#include <NitroModules/NitroHash.hpp>
|
|
144
|
+
#include <NitroModules/CachedProp.hpp>
|
|
194
145
|
|
|
195
146
|
namespace ${namespace} {
|
|
196
147
|
|
|
148
|
+
using namespace facebook;
|
|
149
|
+
|
|
197
150
|
extern const char ${nameVariable}[] = "${T}";
|
|
198
151
|
|
|
199
152
|
${propsClassName}::${propsClassName}(const react::PropsParserContext& context,
|
|
@@ -203,36 +156,11 @@ namespace ${namespace} {
|
|
|
203
156
|
|
|
204
157
|
bool ${propsClassName}::filterObjectKeys(const std::string& propName) {
|
|
205
158
|
switch (hashString(propName)) {
|
|
206
|
-
${indent(
|
|
159
|
+
${indent(filterCases.join('\n'), ' ')}
|
|
207
160
|
default: return false;
|
|
208
161
|
}
|
|
209
162
|
}
|
|
210
163
|
|
|
211
|
-
${descriptorClassName}::${descriptorClassName}(const react::ComponentDescriptorParameters& parameters)
|
|
212
|
-
: ConcreteComponentDescriptor(parameters,
|
|
213
|
-
react::RawPropsParser()) {}
|
|
214
|
-
|
|
215
|
-
std::shared_ptr<const react::Props> ${descriptorClassName}::cloneProps(const react::PropsParserContext& context,
|
|
216
|
-
${descriptorIndent} const std::shared_ptr<const react::Props>& props,
|
|
217
|
-
${descriptorIndent} react::RawProps rawProps) const {
|
|
218
|
-
// 1. Prepare raw props parser
|
|
219
|
-
rawProps.parse(rawPropsParser_);
|
|
220
|
-
// 2. Copy props with Nitro's cached copy constructor
|
|
221
|
-
return ${shadowNodeClassName}::Props(context, /* & */ rawProps, props);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
#ifdef ANDROID
|
|
225
|
-
void ${descriptorClassName}::adopt(react::ShadowNode& shadowNode) const {
|
|
226
|
-
// This is called immediately after \`ShadowNode\` is created, cloned or in progress.
|
|
227
|
-
// On Android, we need to wrap props in our state, which gets routed through Java and later unwrapped in JNI/C++.
|
|
228
|
-
auto& concreteShadowNode = static_cast<${shadowNodeClassName}&>(shadowNode);
|
|
229
|
-
const std::shared_ptr<const ${propsClassName}>& constProps = concreteShadowNode.getConcreteSharedProps();
|
|
230
|
-
const std::shared_ptr<${propsClassName}>& props = std::const_pointer_cast<${propsClassName}>(constProps);
|
|
231
|
-
${stateClassName} state{props};
|
|
232
|
-
concreteShadowNode.setStateData(std::move(state));
|
|
233
|
-
}
|
|
234
|
-
#endif
|
|
235
|
-
|
|
236
164
|
} // namespace ${namespace}
|
|
237
165
|
`.trim();
|
|
238
166
|
const files = [
|
|
@@ -34,6 +34,14 @@ import ${javaNamespace}.*
|
|
|
34
34
|
* Represents the React Native \`ViewManager\` for the "${spec.name}" Nitro HybridView.
|
|
35
35
|
*/
|
|
36
36
|
public class ${manager}: SimpleViewManager<View>() {
|
|
37
|
+
/**
|
|
38
|
+
* Represents the View and its last state snapshot (mutable)
|
|
39
|
+
*/
|
|
40
|
+
private class HybridViewHolder(
|
|
41
|
+
val hybridView: ${viewImplementation},
|
|
42
|
+
var lastState: StateWrapper? = null,
|
|
43
|
+
)
|
|
44
|
+
|
|
37
45
|
init {
|
|
38
46
|
if (RecyclableView::class.java.isAssignableFrom(${viewImplementation}::class.java)) {
|
|
39
47
|
// Enable view recycling
|
|
@@ -48,33 +56,41 @@ public class ${manager}: SimpleViewManager<View>() {
|
|
|
48
56
|
override fun createViewInstance(reactContext: ThemedReactContext): View {
|
|
49
57
|
val hybridView = ${viewImplementation}(reactContext)
|
|
50
58
|
val view = hybridView.view
|
|
51
|
-
view.setTag(associated_hybrid_view_tag, hybridView)
|
|
59
|
+
view.setTag(associated_hybrid_view_tag, HybridViewHolder(hybridView))
|
|
52
60
|
return view
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
override fun updateState(view: View, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? {
|
|
56
|
-
val
|
|
64
|
+
val holder = getHybridViewHolder(view)
|
|
57
65
|
?: throw Error("Couldn't find view $view in local views table!")
|
|
66
|
+
val hybridView = holder.hybridView
|
|
67
|
+
val oldState = holder.lastState
|
|
68
|
+
val newState = stateWrapper
|
|
58
69
|
|
|
59
70
|
// 1. Update each prop individually
|
|
60
71
|
hybridView.beforeUpdate()
|
|
61
|
-
${stateUpdaterName}.updateViewProps(hybridView,
|
|
72
|
+
${stateUpdaterName}.updateViewProps(hybridView, newState, oldState)
|
|
62
73
|
hybridView.afterUpdate()
|
|
74
|
+
holder.lastState = newState
|
|
63
75
|
|
|
64
76
|
// 2. Continue in base View props
|
|
65
|
-
return super.updateState(view, props,
|
|
77
|
+
return super.updateState(view, props, newState)
|
|
66
78
|
}
|
|
67
79
|
|
|
68
80
|
override fun onDropViewInstance(view: View) {
|
|
69
|
-
val
|
|
70
|
-
|
|
81
|
+
val holder = getHybridViewHolder(view)
|
|
82
|
+
holder?.lastState = null
|
|
83
|
+
holder?.hybridView?.onDropView()
|
|
71
84
|
return super.onDropViewInstance(view)
|
|
72
85
|
}
|
|
73
86
|
|
|
74
87
|
protected override fun prepareToRecycleView(reactContext: ThemedReactContext, view: View): View? {
|
|
75
|
-
super.prepareToRecycleView(reactContext, view)
|
|
76
|
-
|
|
88
|
+
val preparedView = super.prepareToRecycleView(reactContext, view)
|
|
89
|
+
?: return null
|
|
90
|
+
val holder = getHybridViewHolder(preparedView)
|
|
77
91
|
?: return null
|
|
92
|
+
val hybridView = holder.hybridView
|
|
93
|
+
holder.lastState = null
|
|
78
94
|
|
|
79
95
|
@Suppress("USELESS_IS_CHECK")
|
|
80
96
|
if (hybridView is RecyclableView) {
|
|
@@ -88,8 +104,8 @@ public class ${manager}: SimpleViewManager<View>() {
|
|
|
88
104
|
}
|
|
89
105
|
}
|
|
90
106
|
|
|
91
|
-
private fun
|
|
92
|
-
return view.getTag(associated_hybrid_view_tag) as?
|
|
107
|
+
private fun getHybridViewHolder(view: View): HybridViewHolder? {
|
|
108
|
+
return view.getTag(associated_hybrid_view_tag) as? HybridViewHolder
|
|
93
109
|
}
|
|
94
110
|
}
|
|
95
111
|
`.trim();
|
|
@@ -105,11 +121,11 @@ internal class ${stateUpdaterName} {
|
|
|
105
121
|
companion object {
|
|
106
122
|
/**
|
|
107
123
|
* Updates the props for [view] through C++.
|
|
108
|
-
* The [
|
|
124
|
+
* The [newState] prop is expected to contain [view]'s props as wrapped Fabric state.
|
|
109
125
|
*/
|
|
110
126
|
@Suppress("KotlinJniMissingFunction")
|
|
111
127
|
@JvmStatic
|
|
112
|
-
external fun updateViewProps(view: ${HybridTSpec},
|
|
128
|
+
external fun updateViewProps(view: ${HybridTSpec}, newState: StateWrapper, oldState: StateWrapper?)
|
|
113
129
|
}
|
|
114
130
|
}
|
|
115
131
|
`.trim();
|
|
@@ -143,7 +159,12 @@ public:
|
|
|
143
159
|
public:
|
|
144
160
|
static void updateViewProps(jni::alias_ref<jni::JClass> /* class */,
|
|
145
161
|
jni::alias_ref<${JHybridTSpec}::JavaPart> view,
|
|
146
|
-
jni::alias_ref<JStateWrapper::javaobject>
|
|
162
|
+
jni::alias_ref<JStateWrapper::javaobject> newState,
|
|
163
|
+
jni::alias_ref<JStateWrapper::javaobject> oldState);
|
|
164
|
+
|
|
165
|
+
private:
|
|
166
|
+
static std::shared_ptr<const ${propsClassName}> getPropsFromStateWrapper(
|
|
167
|
+
jni::alias_ref<JStateWrapper::javaobject> stateWrapper);
|
|
147
168
|
|
|
148
169
|
public:
|
|
149
170
|
static void registerNatives() {
|
|
@@ -164,9 +185,10 @@ public:
|
|
|
164
185
|
const name = escapeCppName(p.name);
|
|
165
186
|
const setter = p.getSetterName('other');
|
|
166
187
|
return `
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
188
|
+
if (oldProps == nullptr
|
|
189
|
+
? newProps->${name}.isProvided()
|
|
190
|
+
: !newProps->${name}.hasSameValue(oldProps->${name})) {
|
|
191
|
+
hybridView->${setter}(newProps->${name}.get());
|
|
170
192
|
}
|
|
171
193
|
`.trim();
|
|
172
194
|
});
|
|
@@ -183,38 +205,55 @@ namespace ${cxxNamespace} {
|
|
|
183
205
|
using namespace facebook;
|
|
184
206
|
using ConcreteStateData = react::ConcreteState<${stateClassName}>;
|
|
185
207
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
208
|
+
std::shared_ptr<const ${propsClassName}> J${stateUpdaterName}::getPropsFromStateWrapper(
|
|
209
|
+
jni::alias_ref<JStateWrapper::javaobject> stateWrapper) {
|
|
210
|
+
if (stateWrapper.get() == nullptr) {
|
|
211
|
+
return nullptr;
|
|
212
|
+
}
|
|
191
213
|
// Get concrete StateWrapperImpl from passed StateWrapper interface object
|
|
192
|
-
jobject rawStateWrapper =
|
|
193
|
-
if (!
|
|
194
|
-
|
|
214
|
+
jobject rawStateWrapper = stateWrapper.get();
|
|
215
|
+
if (!stateWrapper->isInstanceOf(react::StateWrapperImpl::javaClassStatic())) [[unlikely]] {
|
|
216
|
+
throw std::runtime_error("StateWrapper is not a StateWrapperImpl");
|
|
217
|
+
}
|
|
218
|
+
auto stateWrapperImpl = jni::alias_ref<react::StateWrapperImpl::javaobject>{
|
|
219
|
+
static_cast<react::StateWrapperImpl::javaobject>(rawStateWrapper)
|
|
220
|
+
};
|
|
221
|
+
std::shared_ptr<const react::State> state = stateWrapperImpl->cthis()->getState();
|
|
222
|
+
if (state == nullptr) {
|
|
223
|
+
return nullptr;
|
|
195
224
|
}
|
|
196
|
-
auto stateWrapper = jni::alias_ref<react::StateWrapperImpl::javaobject>{
|
|
197
|
-
static_cast<react::StateWrapperImpl::javaobject>(rawStateWrapper)};
|
|
198
|
-
std::shared_ptr<const react::State> state = stateWrapper->cthis()->getState();
|
|
199
225
|
auto concreteState = std::static_pointer_cast<const ConcreteStateData>(state);
|
|
200
226
|
const ${stateClassName}& data = concreteState->getData();
|
|
201
|
-
const std::shared_ptr
|
|
227
|
+
const std::shared_ptr<const ${propsClassName}>& props = data.getProps();
|
|
202
228
|
if (props == nullptr) [[unlikely]] {
|
|
203
|
-
// Props aren't set yet!
|
|
204
229
|
throw std::runtime_error("${stateClassName}'s data doesn't contain any props!");
|
|
205
230
|
}
|
|
231
|
+
return props;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
void J${stateUpdaterName}::updateViewProps(jni::alias_ref<jni::JClass> /* class */,
|
|
235
|
+
jni::alias_ref<${JHybridTSpec}::JavaPart> javaView,
|
|
236
|
+
jni::alias_ref<JStateWrapper::javaobject> newState,
|
|
237
|
+
jni::alias_ref<JStateWrapper::javaobject> oldState) {
|
|
238
|
+
std::shared_ptr<${JHybridTSpec}> hybridView = javaView->get${JHybridTSpec}();
|
|
239
|
+
std::shared_ptr<const ${propsClassName}> newProps = getPropsFromStateWrapper(newState);
|
|
240
|
+
std::shared_ptr<const ${propsClassName}> oldProps = getPropsFromStateWrapper(oldState);
|
|
241
|
+
if (newProps == nullptr) [[unlikely]] {
|
|
242
|
+
throw std::runtime_error("Current StateWrapper doesn't contain any props!");
|
|
243
|
+
}
|
|
206
244
|
|
|
207
|
-
// Update
|
|
245
|
+
// Update only props that differ from the previous State snapshot.
|
|
208
246
|
${indent(propsUpdaterCalls.join('\n'), ' ')}
|
|
209
247
|
|
|
210
248
|
// Update hybridRef if it changed
|
|
211
|
-
if (
|
|
249
|
+
if (oldProps == nullptr
|
|
250
|
+
? newProps->hybridRef.isProvided()
|
|
251
|
+
: !newProps->hybridRef.hasSameValue(oldProps->hybridRef)) {
|
|
212
252
|
// hybridRef changed - call it with new this
|
|
213
|
-
const auto& maybeFunc =
|
|
253
|
+
const auto& maybeFunc = newProps->hybridRef.get();
|
|
214
254
|
if (maybeFunc.has_value()) {
|
|
215
255
|
maybeFunc.value()(hybridView);
|
|
216
256
|
}
|
|
217
|
-
props->hybridRef.isDirty = false;
|
|
218
257
|
}
|
|
219
258
|
}
|
|
220
259
|
|
|
@@ -10,7 +10,7 @@ export function createSwiftHybridViewManager(spec) {
|
|
|
10
10
|
const namespace = spec.config.getCxxNamespace('c++');
|
|
11
11
|
const swiftNamespace = spec.config.getIosModuleName();
|
|
12
12
|
const { HybridTSpec, HybridTSpecSwift, HybridTSpecCxx } = getHybridObjectName(spec.name);
|
|
13
|
-
const { component, descriptorClassName, propsClassName } = getViewComponentNames(spec);
|
|
13
|
+
const { component, descriptorClassName, propsClassName, shadowNodeClassName, } = getViewComponentNames(spec);
|
|
14
14
|
const implementation = spec.config.getIosAutolinkedImplementation(spec.name);
|
|
15
15
|
if (implementation?.language !== 'swift') {
|
|
16
16
|
throw new Error(`Cannot create Swift HybridView ViewManager for ${spec.name} - it must be autolinked with a Swift iOS implementation in nitro.json!`);
|
|
@@ -19,12 +19,13 @@ export function createSwiftHybridViewManager(spec) {
|
|
|
19
19
|
const name = escapeCppName(p.name);
|
|
20
20
|
const setter = p.getSetterName('swift');
|
|
21
21
|
const bridge = new SwiftCxxBridgedType(p.type, false);
|
|
22
|
-
const parse = bridge.parseFromCppToSwift(`newViewProps.${name}.
|
|
22
|
+
const parse = bridge.parseFromCppToSwift(`newViewProps.${name}.get()`, 'c++');
|
|
23
23
|
return `
|
|
24
24
|
// ${p.jsSignature}
|
|
25
|
-
if (
|
|
25
|
+
if (oldViewProps == nullptr
|
|
26
|
+
? newViewProps.${name}.isProvided()
|
|
27
|
+
: !newViewProps.${name}.hasSameValue(oldViewProps->${name})) {
|
|
26
28
|
swiftPart.${setter}(${indent(parse, ' ')});
|
|
27
|
-
newViewProps.${name}.isDirty = false;
|
|
28
29
|
}
|
|
29
30
|
`.trim();
|
|
30
31
|
});
|
|
@@ -63,6 +64,7 @@ using namespace ${namespace}::views;
|
|
|
63
64
|
|
|
64
65
|
@implementation ${component} {
|
|
65
66
|
std::shared_ptr<${HybridTSpecSwift}> _hybridView;
|
|
67
|
+
BOOL _didDropView;
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
+ (void) load {
|
|
@@ -76,6 +78,7 @@ using namespace ${namespace}::views;
|
|
|
76
78
|
|
|
77
79
|
- (instancetype) init {
|
|
78
80
|
if (self = [super init]) {
|
|
81
|
+
_props = ${shadowNodeClassName}::defaultSharedProps();
|
|
79
82
|
std::shared_ptr<${HybridTSpec}> hybridView = ${getHybridObjectConstructorCall(spec.name)}
|
|
80
83
|
_hybridView = std::dynamic_pointer_cast<${HybridTSpecSwift}>(hybridView);
|
|
81
84
|
[self updateView];
|
|
@@ -95,31 +98,50 @@ using namespace ${namespace}::views;
|
|
|
95
98
|
[self setContentView:view];
|
|
96
99
|
}
|
|
97
100
|
|
|
101
|
+
- (void) notifyOnDropView {
|
|
102
|
+
// A recycled component can later be invalidated. Notify only once per mount.
|
|
103
|
+
if (_didDropView) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
${swiftNamespace}::${HybridTSpecCxx}& swiftPart = _hybridView->getSwiftPart();
|
|
107
|
+
swiftPart.onDropView();
|
|
108
|
+
_didDropView = YES;
|
|
109
|
+
}
|
|
110
|
+
|
|
98
111
|
- (void) updateProps:(const std::shared_ptr<const react::Props>&)props
|
|
99
112
|
oldProps:(const std::shared_ptr<const react::Props>&)oldProps {
|
|
113
|
+
// A props update marks a newly mounted or still-active component.
|
|
114
|
+
_didDropView = NO;
|
|
115
|
+
|
|
100
116
|
// 1. Downcast props
|
|
101
|
-
const auto&
|
|
102
|
-
auto
|
|
117
|
+
const auto& newViewProps = *std::static_pointer_cast<const ${propsClassName}>(props);
|
|
118
|
+
const auto* oldViewProps = static_cast<const ${propsClassName}*>(oldProps.get());
|
|
103
119
|
${swiftNamespace}::${HybridTSpecCxx}& swiftPart = _hybridView->getSwiftPart();
|
|
104
120
|
|
|
105
|
-
// 2. Update
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// hybridRef
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
// 2. Update only props that differ from the previous Props snapshot.
|
|
122
|
+
const bool hasTransactionPropChanges = oldViewProps == nullptr
|
|
123
|
+
? newViewProps.hasAnyProvidedProps()
|
|
124
|
+
: !newViewProps.hasSameProps(*oldViewProps);
|
|
125
|
+
if (hasTransactionPropChanges) {
|
|
126
|
+
swiftPart.beforeUpdate();
|
|
127
|
+
|
|
128
|
+
${indent(propAssignments.join('\n'), ' ')}
|
|
129
|
+
|
|
130
|
+
// Update hybridRef if it changed
|
|
131
|
+
if (oldViewProps == nullptr
|
|
132
|
+
? newViewProps.hybridRef.isProvided()
|
|
133
|
+
: !newViewProps.hybridRef.hasSameValue(oldViewProps->hybridRef)) {
|
|
134
|
+
// hybridRef changed - call it with new this
|
|
135
|
+
const auto& maybeFunc = newViewProps.hybridRef.get();
|
|
136
|
+
if (maybeFunc.has_value()) {
|
|
137
|
+
maybeFunc.value()(_hybridView);
|
|
138
|
+
}
|
|
118
139
|
}
|
|
119
|
-
|
|
140
|
+
|
|
141
|
+
swiftPart.afterUpdate();
|
|
120
142
|
}
|
|
121
143
|
|
|
122
|
-
//
|
|
144
|
+
// 3. Continue in base class
|
|
123
145
|
[super updateProps:props oldProps:oldProps];
|
|
124
146
|
}
|
|
125
147
|
|
|
@@ -128,6 +150,7 @@ using namespace ${namespace}::views;
|
|
|
128
150
|
}
|
|
129
151
|
|
|
130
152
|
- (void)prepareForRecycle {
|
|
153
|
+
[self notifyOnDropView];
|
|
131
154
|
[super prepareForRecycle];
|
|
132
155
|
${swiftNamespace}::${HybridTSpecCxx}& swiftPart = _hybridView->getSwiftPart();
|
|
133
156
|
swiftPart.maybePrepareForRecycle();
|
|
@@ -135,8 +158,7 @@ using namespace ${namespace}::views;
|
|
|
135
158
|
|
|
136
159
|
#ifdef ENABLE_RCT_COMPONENT_VIEW_INVALIDATE
|
|
137
160
|
- (void)invalidate {
|
|
138
|
-
|
|
139
|
-
swiftPart.onDropView();
|
|
161
|
+
[self notifyOnDropView];
|
|
140
162
|
[super invalidate];
|
|
141
163
|
}
|
|
142
164
|
#endif
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitrogen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0-beta.0",
|
|
4
4
|
"description": "Code generator for React Native Nitro Modules that turns TypeScript specs into C++, Swift, and Kotlin bindings.",
|
|
5
5
|
"main": "lib/index",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"chalk": "^5.3.0",
|
|
38
|
-
"react-native-nitro-modules": "^0.
|
|
38
|
+
"react-native-nitro-modules": "^0.37.0-beta.0",
|
|
39
39
|
"ts-morph": "^28.0.0",
|
|
40
40
|
"yargs": "^18.0.0",
|
|
41
41
|
"zod": "^4.4.3"
|
package/src/syntax/createType.ts
CHANGED
|
@@ -69,6 +69,16 @@ function getFunctionCallSignature(func: TSMorphType): Signature {
|
|
|
69
69
|
return callSignature
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Whether the given type is optional (`T | undefined`) or not.
|
|
74
|
+
*
|
|
75
|
+
* Note that `null` is a separate type in Nitro (`NullType`) - only `undefined`
|
|
76
|
+
* (or a `?`) makes a type optional.
|
|
77
|
+
*/
|
|
78
|
+
function isOptionalType(type: TSMorphType): boolean {
|
|
79
|
+
return type.getUnionTypes().some((t) => t.isUndefined())
|
|
80
|
+
}
|
|
81
|
+
|
|
72
82
|
function removeDuplicates(types: Type[]): Type[] {
|
|
73
83
|
return types.filter((t1, index, array) => {
|
|
74
84
|
const firstIndexOfType = array.findIndex(
|
|
@@ -269,7 +279,11 @@ export function createType(
|
|
|
269
279
|
return new VoidType()
|
|
270
280
|
} else if (type.isArray()) {
|
|
271
281
|
const arrayElementType = type.getArrayElementTypeOrThrow()
|
|
272
|
-
const elementType = createType(
|
|
282
|
+
const elementType = createType(
|
|
283
|
+
language,
|
|
284
|
+
arrayElementType,
|
|
285
|
+
isOptionalType(arrayElementType)
|
|
286
|
+
)
|
|
273
287
|
return new ArrayType(elementType)
|
|
274
288
|
} else if (type.isTuple()) {
|
|
275
289
|
const itemTypes = type
|
|
@@ -280,10 +294,11 @@ export function createType(
|
|
|
280
294
|
// It's a function!
|
|
281
295
|
const callSignature = getFunctionCallSignature(type)
|
|
282
296
|
const funcReturnType = callSignature.getReturnType()
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
297
|
+
const returnType = createType(
|
|
298
|
+
language,
|
|
299
|
+
funcReturnType,
|
|
300
|
+
isOptionalType(funcReturnType)
|
|
301
|
+
)
|
|
287
302
|
const parameters = callSignature.getParameters().map((p) => {
|
|
288
303
|
const declaration = p.getValueDeclarationOrThrow()
|
|
289
304
|
const parameterType = p.getTypeAtLocation(declaration)
|
|
@@ -295,20 +310,21 @@ export function createType(
|
|
|
295
310
|
} else if (isPromise(type)) {
|
|
296
311
|
// It's a Promise!
|
|
297
312
|
const [promiseResolvingType] = getArguments(type, 'Promise', 1)
|
|
298
|
-
const isResolvingOptional = promiseResolvingType
|
|
299
|
-
.getUnionTypes()
|
|
300
|
-
.some((t) => t.isUndefined())
|
|
301
313
|
const resolvingType = createType(
|
|
302
314
|
language,
|
|
303
315
|
promiseResolvingType,
|
|
304
|
-
|
|
316
|
+
isOptionalType(promiseResolvingType)
|
|
305
317
|
)
|
|
306
318
|
return new PromiseType(resolvingType)
|
|
307
319
|
} else if (isRecord(type)) {
|
|
308
320
|
// Record<K, V> -> unordered_map<K, V>
|
|
309
321
|
const [keyTypeT, valueTypeT] = getArguments(type, 'Record', 2)
|
|
310
322
|
const keyType = createType(language, keyTypeT, false)
|
|
311
|
-
const valueType = createType(
|
|
323
|
+
const valueType = createType(
|
|
324
|
+
language,
|
|
325
|
+
valueTypeT,
|
|
326
|
+
isOptionalType(valueTypeT)
|
|
327
|
+
)
|
|
312
328
|
return new RecordType(keyType, valueType)
|
|
313
329
|
} else if (isArrayBuffer(type)) {
|
|
314
330
|
// ArrayBuffer
|
|
@@ -469,6 +469,13 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
|
|
|
469
469
|
|
|
470
470
|
dereferenceToJObject(parameterName: string): string {
|
|
471
471
|
switch (this.type.kind) {
|
|
472
|
+
// an optional bridges to a (possibly `nullptr`) ref of its wrapping type,
|
|
473
|
+
// so it needs to be dereferenced the same way as the type it wraps.
|
|
474
|
+
case 'optional': {
|
|
475
|
+
const optional = getTypeAs(this.type, OptionalType)
|
|
476
|
+
const bridge = new KotlinCxxBridgedType(optional.wrappingType)
|
|
477
|
+
return bridge.dereferenceToJObject(parameterName)
|
|
478
|
+
}
|
|
472
479
|
// any jni::HybridClass needs to be dereferenced to jobject with .get()
|
|
473
480
|
case 'array-buffer':
|
|
474
481
|
case 'function':
|
|
@@ -4,7 +4,6 @@ import { createIndentation, indent } from '../utils.js'
|
|
|
4
4
|
import {
|
|
5
5
|
createFileMetadataString,
|
|
6
6
|
escapeCppName,
|
|
7
|
-
isFunction,
|
|
8
7
|
isNotDuplicate,
|
|
9
8
|
} from '../syntax/helpers.js'
|
|
10
9
|
import { getHybridObjectName } from '../syntax/getHybridObjectName.js'
|
|
@@ -73,9 +72,20 @@ export function createViewComponentShadowNodeFiles(
|
|
|
73
72
|
|
|
74
73
|
const props = [...spec.properties, getHybridRefProperty(spec)]
|
|
75
74
|
const properties = props.map(
|
|
76
|
-
(p) =>
|
|
75
|
+
(p) =>
|
|
76
|
+
`nitro::CachedProp<${p.type.getCode('c++')}> ${escapeCppName(p.name)};`
|
|
77
77
|
)
|
|
78
|
-
const
|
|
78
|
+
const filterCases = props.map(
|
|
79
|
+
(prop) => `case hashString("${prop.name}"): return true;`
|
|
80
|
+
)
|
|
81
|
+
const comparisons = props.map((prop) => {
|
|
82
|
+
const name = escapeCppName(prop.name)
|
|
83
|
+
return `${name}.hasSameValue(other.${name})`
|
|
84
|
+
})
|
|
85
|
+
const providedChecks = props.map((prop) => {
|
|
86
|
+
const name = escapeCppName(prop.name)
|
|
87
|
+
return `${name}.isProvided()`
|
|
88
|
+
})
|
|
79
89
|
const includes = props
|
|
80
90
|
.flatMap((p) =>
|
|
81
91
|
p.getRequiredImports('c++').map((i) => includeHeader(i, true))
|
|
@@ -89,14 +99,15 @@ ${createFileMetadataString(`${component}.hpp`)}
|
|
|
89
99
|
|
|
90
100
|
#pragma once
|
|
91
101
|
|
|
92
|
-
#include <optional>
|
|
93
|
-
#include <NitroModules/NitroDefines.hpp>
|
|
94
|
-
#include <NitroModules/NitroHash.hpp>
|
|
95
102
|
#include <NitroModules/CachedProp.hpp>
|
|
96
|
-
#include <
|
|
97
|
-
#include <
|
|
103
|
+
#include <NitroModules/ViewComponentDescriptor.hpp>
|
|
104
|
+
#include <NitroModules/ViewPropsHolderState.hpp>
|
|
98
105
|
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
|
99
106
|
#include <react/renderer/components/view/ViewProps.h>
|
|
107
|
+
#include <react/renderer/core/PropsParserContext.h>
|
|
108
|
+
#include <react/renderer/core/RawProps.h>
|
|
109
|
+
|
|
110
|
+
#include <string>
|
|
100
111
|
|
|
101
112
|
${includes.join('\n')}
|
|
102
113
|
|
|
@@ -122,6 +133,16 @@ namespace ${namespace} {
|
|
|
122
133
|
public:
|
|
123
134
|
${indent(properties.join('\n'), ' ')}
|
|
124
135
|
|
|
136
|
+
[[nodiscard]]
|
|
137
|
+
bool hasSameProps(const ${propsClassName}& other) const noexcept {
|
|
138
|
+
return ${comparisons.join(' &&\n ')};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
[[nodiscard]]
|
|
142
|
+
bool hasAnyProvidedProps() const noexcept {
|
|
143
|
+
return ${providedChecks.join(' ||\n ')};
|
|
144
|
+
}
|
|
145
|
+
|
|
125
146
|
private:
|
|
126
147
|
static bool filterObjectKeys(const std::string& propName);
|
|
127
148
|
};
|
|
@@ -129,32 +150,7 @@ namespace ${namespace} {
|
|
|
129
150
|
/**
|
|
130
151
|
* State for the "${spec.name}" View.
|
|
131
152
|
*/
|
|
132
|
-
|
|
133
|
-
public:
|
|
134
|
-
${stateClassName}() = default;
|
|
135
|
-
explicit ${stateClassName}(const std::shared_ptr<${propsClassName}>& props):
|
|
136
|
-
_props(props) {}
|
|
137
|
-
|
|
138
|
-
public:
|
|
139
|
-
[[nodiscard]]
|
|
140
|
-
const std::shared_ptr<${propsClassName}>& getProps() const {
|
|
141
|
-
return _props;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
public:
|
|
145
|
-
#ifdef ANDROID
|
|
146
|
-
${stateClassName}(const ${stateClassName}& /* previousState */, folly::dynamic /* data */) {}
|
|
147
|
-
folly::dynamic getDynamic() const {
|
|
148
|
-
throw std::runtime_error("${stateClassName} does not support folly!");
|
|
149
|
-
}
|
|
150
|
-
react::MapBuffer getMapBuffer() const {
|
|
151
|
-
throw std::runtime_error("${stateClassName} does not support MapBuffer!");
|
|
152
|
-
};
|
|
153
|
-
#endif
|
|
154
|
-
|
|
155
|
-
private:
|
|
156
|
-
std::shared_ptr<${propsClassName}> _props;
|
|
157
|
-
};
|
|
153
|
+
using ${stateClassName} = nitro::ViewPropsHolderState<${propsClassName}>;
|
|
158
154
|
|
|
159
155
|
/**
|
|
160
156
|
* The Shadow Node for the "${spec.name}" View.
|
|
@@ -167,80 +163,34 @@ namespace ${namespace} {
|
|
|
167
163
|
/**
|
|
168
164
|
* The Component Descriptor for the "${spec.name}" View.
|
|
169
165
|
*/
|
|
170
|
-
|
|
171
|
-
public:
|
|
172
|
-
explicit ${descriptorClassName}(const react::ComponentDescriptorParameters& parameters);
|
|
173
|
-
|
|
174
|
-
public:
|
|
175
|
-
/**
|
|
176
|
-
* A faster path for cloning props - reuses the caching logic from \`${propsClassName}\`.
|
|
177
|
-
*/
|
|
178
|
-
std::shared_ptr<const react::Props> cloneProps(const react::PropsParserContext& context,
|
|
179
|
-
const std::shared_ptr<const react::Props>& props,
|
|
180
|
-
react::RawProps rawProps) const override;
|
|
181
|
-
#ifdef ANDROID
|
|
182
|
-
void adopt(react::ShadowNode& shadowNode) const override;
|
|
183
|
-
#endif
|
|
184
|
-
};
|
|
166
|
+
using ${descriptorClassName} = nitro::ViewComponentDescriptor<${shadowNodeClassName}>;
|
|
185
167
|
|
|
186
168
|
/* The actual view for "${spec.name}" needs to be implemented in platform-specific code. */
|
|
187
169
|
|
|
188
170
|
} // namespace ${namespace}
|
|
189
171
|
`.trim()
|
|
190
172
|
|
|
191
|
-
// .cpp code
|
|
192
173
|
const propInitializers = [
|
|
193
174
|
'react::ViewProps(context, sourceProps, rawProps, filterObjectKeys)',
|
|
175
|
+
...props.map((prop) => {
|
|
176
|
+
const name = escapeCppName(prop.name)
|
|
177
|
+
const type = prop.type.getCode('c++')
|
|
178
|
+
return `${name}(nitro::CachedProp<${type}>::fromRawValue("${spec.name}", "${prop.name}", rawProps, sourceProps.${name}))`
|
|
179
|
+
}),
|
|
194
180
|
]
|
|
195
|
-
const propCopyInitializers = ['react::ViewProps()']
|
|
196
|
-
for (const prop of props) {
|
|
197
|
-
const name = escapeCppName(prop.name)
|
|
198
|
-
const type = prop.type.getCode('c++')
|
|
199
|
-
|
|
200
|
-
let valueConversion = `value`
|
|
201
|
-
if (isFunction(prop.type)) {
|
|
202
|
-
// Due to a React limitation, functions cannot be passed to native directly,
|
|
203
|
-
// because RN converts them to booleans (`true`). Nitro knows this and just
|
|
204
|
-
// wraps functions as objects - the original function is stored in `f`.
|
|
205
|
-
valueConversion = `value.asObject(*runtime).getProperty(*runtime, PropNameIDCache::get(*runtime, "f"))`
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
propInitializers.push(
|
|
209
|
-
`
|
|
210
|
-
${name}([&]() -> CachedProp<${type}> {
|
|
211
|
-
try {
|
|
212
|
-
const react::RawValue* rawValue = rawProps.at("${prop.name}", nullptr, nullptr);
|
|
213
|
-
if (rawValue == nullptr) return sourceProps.${name};
|
|
214
|
-
const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
|
|
215
|
-
return CachedProp<${type}>::fromRawValue(*runtime, ${valueConversion}, sourceProps.${name});
|
|
216
|
-
} catch (const std::exception& exc) {
|
|
217
|
-
throw std::runtime_error(std::string("${spec.name}.${prop.name}: ") + exc.what());
|
|
218
|
-
}
|
|
219
|
-
}())`.trim()
|
|
220
|
-
)
|
|
221
|
-
propCopyInitializers.push(`${name}(other.${name})`)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
181
|
const ctorIndent = createIndentation(propsClassName.length * 2)
|
|
225
|
-
const descriptorIndent = createIndentation(descriptorClassName.length)
|
|
226
182
|
const componentCode = `
|
|
227
183
|
${createFileMetadataString(`${component}.cpp`)}
|
|
228
184
|
|
|
229
185
|
#include "${component}.hpp"
|
|
230
186
|
|
|
231
|
-
#include <
|
|
232
|
-
#include <
|
|
233
|
-
#include <utility>
|
|
234
|
-
#include <NitroModules/NitroDefines.hpp>
|
|
235
|
-
#include <NitroModules/JSIConverter.hpp>
|
|
236
|
-
#include <NitroModules/PropNameIDCache.hpp>
|
|
237
|
-
#include <react/renderer/core/RawValue.h>
|
|
238
|
-
#include <react/renderer/core/ShadowNode.h>
|
|
239
|
-
#include <react/renderer/core/ComponentDescriptor.h>
|
|
240
|
-
#include <react/renderer/components/view/ViewProps.h>
|
|
187
|
+
#include <NitroModules/NitroHash.hpp>
|
|
188
|
+
#include <NitroModules/CachedProp.hpp>
|
|
241
189
|
|
|
242
190
|
namespace ${namespace} {
|
|
243
191
|
|
|
192
|
+
using namespace facebook;
|
|
193
|
+
|
|
244
194
|
extern const char ${nameVariable}[] = "${T}";
|
|
245
195
|
|
|
246
196
|
${propsClassName}::${propsClassName}(const react::PropsParserContext& context,
|
|
@@ -250,36 +200,11 @@ namespace ${namespace} {
|
|
|
250
200
|
|
|
251
201
|
bool ${propsClassName}::filterObjectKeys(const std::string& propName) {
|
|
252
202
|
switch (hashString(propName)) {
|
|
253
|
-
${indent(
|
|
203
|
+
${indent(filterCases.join('\n'), ' ')}
|
|
254
204
|
default: return false;
|
|
255
205
|
}
|
|
256
206
|
}
|
|
257
207
|
|
|
258
|
-
${descriptorClassName}::${descriptorClassName}(const react::ComponentDescriptorParameters& parameters)
|
|
259
|
-
: ConcreteComponentDescriptor(parameters,
|
|
260
|
-
react::RawPropsParser()) {}
|
|
261
|
-
|
|
262
|
-
std::shared_ptr<const react::Props> ${descriptorClassName}::cloneProps(const react::PropsParserContext& context,
|
|
263
|
-
${descriptorIndent} const std::shared_ptr<const react::Props>& props,
|
|
264
|
-
${descriptorIndent} react::RawProps rawProps) const {
|
|
265
|
-
// 1. Prepare raw props parser
|
|
266
|
-
rawProps.parse(rawPropsParser_);
|
|
267
|
-
// 2. Copy props with Nitro's cached copy constructor
|
|
268
|
-
return ${shadowNodeClassName}::Props(context, /* & */ rawProps, props);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
#ifdef ANDROID
|
|
272
|
-
void ${descriptorClassName}::adopt(react::ShadowNode& shadowNode) const {
|
|
273
|
-
// This is called immediately after \`ShadowNode\` is created, cloned or in progress.
|
|
274
|
-
// On Android, we need to wrap props in our state, which gets routed through Java and later unwrapped in JNI/C++.
|
|
275
|
-
auto& concreteShadowNode = static_cast<${shadowNodeClassName}&>(shadowNode);
|
|
276
|
-
const std::shared_ptr<const ${propsClassName}>& constProps = concreteShadowNode.getConcreteSharedProps();
|
|
277
|
-
const std::shared_ptr<${propsClassName}>& props = std::const_pointer_cast<${propsClassName}>(constProps);
|
|
278
|
-
${stateClassName} state{props};
|
|
279
|
-
concreteShadowNode.setStateData(std::move(state));
|
|
280
|
-
}
|
|
281
|
-
#endif
|
|
282
|
-
|
|
283
208
|
} // namespace ${namespace}
|
|
284
209
|
`.trim()
|
|
285
210
|
|
|
@@ -56,6 +56,14 @@ import ${javaNamespace}.*
|
|
|
56
56
|
* Represents the React Native \`ViewManager\` for the "${spec.name}" Nitro HybridView.
|
|
57
57
|
*/
|
|
58
58
|
public class ${manager}: SimpleViewManager<View>() {
|
|
59
|
+
/**
|
|
60
|
+
* Represents the View and its last state snapshot (mutable)
|
|
61
|
+
*/
|
|
62
|
+
private class HybridViewHolder(
|
|
63
|
+
val hybridView: ${viewImplementation},
|
|
64
|
+
var lastState: StateWrapper? = null,
|
|
65
|
+
)
|
|
66
|
+
|
|
59
67
|
init {
|
|
60
68
|
if (RecyclableView::class.java.isAssignableFrom(${viewImplementation}::class.java)) {
|
|
61
69
|
// Enable view recycling
|
|
@@ -70,33 +78,41 @@ public class ${manager}: SimpleViewManager<View>() {
|
|
|
70
78
|
override fun createViewInstance(reactContext: ThemedReactContext): View {
|
|
71
79
|
val hybridView = ${viewImplementation}(reactContext)
|
|
72
80
|
val view = hybridView.view
|
|
73
|
-
view.setTag(associated_hybrid_view_tag, hybridView)
|
|
81
|
+
view.setTag(associated_hybrid_view_tag, HybridViewHolder(hybridView))
|
|
74
82
|
return view
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
override fun updateState(view: View, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? {
|
|
78
|
-
val
|
|
86
|
+
val holder = getHybridViewHolder(view)
|
|
79
87
|
?: throw Error("Couldn't find view $view in local views table!")
|
|
88
|
+
val hybridView = holder.hybridView
|
|
89
|
+
val oldState = holder.lastState
|
|
90
|
+
val newState = stateWrapper
|
|
80
91
|
|
|
81
92
|
// 1. Update each prop individually
|
|
82
93
|
hybridView.beforeUpdate()
|
|
83
|
-
${stateUpdaterName}.updateViewProps(hybridView,
|
|
94
|
+
${stateUpdaterName}.updateViewProps(hybridView, newState, oldState)
|
|
84
95
|
hybridView.afterUpdate()
|
|
96
|
+
holder.lastState = newState
|
|
85
97
|
|
|
86
98
|
// 2. Continue in base View props
|
|
87
|
-
return super.updateState(view, props,
|
|
99
|
+
return super.updateState(view, props, newState)
|
|
88
100
|
}
|
|
89
101
|
|
|
90
102
|
override fun onDropViewInstance(view: View) {
|
|
91
|
-
val
|
|
92
|
-
|
|
103
|
+
val holder = getHybridViewHolder(view)
|
|
104
|
+
holder?.lastState = null
|
|
105
|
+
holder?.hybridView?.onDropView()
|
|
93
106
|
return super.onDropViewInstance(view)
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
protected override fun prepareToRecycleView(reactContext: ThemedReactContext, view: View): View? {
|
|
97
|
-
super.prepareToRecycleView(reactContext, view)
|
|
98
|
-
val hybridView = getHybridView(view)
|
|
110
|
+
val preparedView = super.prepareToRecycleView(reactContext, view)
|
|
99
111
|
?: return null
|
|
112
|
+
val holder = getHybridViewHolder(preparedView)
|
|
113
|
+
?: return null
|
|
114
|
+
val hybridView = holder.hybridView
|
|
115
|
+
holder.lastState = null
|
|
100
116
|
|
|
101
117
|
@Suppress("USELESS_IS_CHECK")
|
|
102
118
|
if (hybridView is RecyclableView) {
|
|
@@ -110,8 +126,8 @@ public class ${manager}: SimpleViewManager<View>() {
|
|
|
110
126
|
}
|
|
111
127
|
}
|
|
112
128
|
|
|
113
|
-
private fun
|
|
114
|
-
return view.getTag(associated_hybrid_view_tag) as?
|
|
129
|
+
private fun getHybridViewHolder(view: View): HybridViewHolder? {
|
|
130
|
+
return view.getTag(associated_hybrid_view_tag) as? HybridViewHolder
|
|
115
131
|
}
|
|
116
132
|
}
|
|
117
133
|
`.trim()
|
|
@@ -128,11 +144,11 @@ internal class ${stateUpdaterName} {
|
|
|
128
144
|
companion object {
|
|
129
145
|
/**
|
|
130
146
|
* Updates the props for [view] through C++.
|
|
131
|
-
* The [
|
|
147
|
+
* The [newState] prop is expected to contain [view]'s props as wrapped Fabric state.
|
|
132
148
|
*/
|
|
133
149
|
@Suppress("KotlinJniMissingFunction")
|
|
134
150
|
@JvmStatic
|
|
135
|
-
external fun updateViewProps(view: ${HybridTSpec},
|
|
151
|
+
external fun updateViewProps(view: ${HybridTSpec}, newState: StateWrapper, oldState: StateWrapper?)
|
|
136
152
|
}
|
|
137
153
|
}
|
|
138
154
|
`.trim()
|
|
@@ -171,7 +187,12 @@ public:
|
|
|
171
187
|
public:
|
|
172
188
|
static void updateViewProps(jni::alias_ref<jni::JClass> /* class */,
|
|
173
189
|
jni::alias_ref<${JHybridTSpec}::JavaPart> view,
|
|
174
|
-
jni::alias_ref<JStateWrapper::javaobject>
|
|
190
|
+
jni::alias_ref<JStateWrapper::javaobject> newState,
|
|
191
|
+
jni::alias_ref<JStateWrapper::javaobject> oldState);
|
|
192
|
+
|
|
193
|
+
private:
|
|
194
|
+
static std::shared_ptr<const ${propsClassName}> getPropsFromStateWrapper(
|
|
195
|
+
jni::alias_ref<JStateWrapper::javaobject> stateWrapper);
|
|
175
196
|
|
|
176
197
|
public:
|
|
177
198
|
static void registerNatives() {
|
|
@@ -193,9 +214,10 @@ public:
|
|
|
193
214
|
const name = escapeCppName(p.name)
|
|
194
215
|
const setter = p.getSetterName('other')
|
|
195
216
|
return `
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
217
|
+
if (oldProps == nullptr
|
|
218
|
+
? newProps->${name}.isProvided()
|
|
219
|
+
: !newProps->${name}.hasSameValue(oldProps->${name})) {
|
|
220
|
+
hybridView->${setter}(newProps->${name}.get());
|
|
199
221
|
}
|
|
200
222
|
`.trim()
|
|
201
223
|
})
|
|
@@ -212,38 +234,55 @@ namespace ${cxxNamespace} {
|
|
|
212
234
|
using namespace facebook;
|
|
213
235
|
using ConcreteStateData = react::ConcreteState<${stateClassName}>;
|
|
214
236
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
237
|
+
std::shared_ptr<const ${propsClassName}> J${stateUpdaterName}::getPropsFromStateWrapper(
|
|
238
|
+
jni::alias_ref<JStateWrapper::javaobject> stateWrapper) {
|
|
239
|
+
if (stateWrapper.get() == nullptr) {
|
|
240
|
+
return nullptr;
|
|
241
|
+
}
|
|
220
242
|
// Get concrete StateWrapperImpl from passed StateWrapper interface object
|
|
221
|
-
jobject rawStateWrapper =
|
|
222
|
-
if (!
|
|
223
|
-
|
|
243
|
+
jobject rawStateWrapper = stateWrapper.get();
|
|
244
|
+
if (!stateWrapper->isInstanceOf(react::StateWrapperImpl::javaClassStatic())) [[unlikely]] {
|
|
245
|
+
throw std::runtime_error("StateWrapper is not a StateWrapperImpl");
|
|
246
|
+
}
|
|
247
|
+
auto stateWrapperImpl = jni::alias_ref<react::StateWrapperImpl::javaobject>{
|
|
248
|
+
static_cast<react::StateWrapperImpl::javaobject>(rawStateWrapper)
|
|
249
|
+
};
|
|
250
|
+
std::shared_ptr<const react::State> state = stateWrapperImpl->cthis()->getState();
|
|
251
|
+
if (state == nullptr) {
|
|
252
|
+
return nullptr;
|
|
224
253
|
}
|
|
225
|
-
auto stateWrapper = jni::alias_ref<react::StateWrapperImpl::javaobject>{
|
|
226
|
-
static_cast<react::StateWrapperImpl::javaobject>(rawStateWrapper)};
|
|
227
|
-
std::shared_ptr<const react::State> state = stateWrapper->cthis()->getState();
|
|
228
254
|
auto concreteState = std::static_pointer_cast<const ConcreteStateData>(state);
|
|
229
255
|
const ${stateClassName}& data = concreteState->getData();
|
|
230
|
-
const std::shared_ptr
|
|
256
|
+
const std::shared_ptr<const ${propsClassName}>& props = data.getProps();
|
|
231
257
|
if (props == nullptr) [[unlikely]] {
|
|
232
|
-
// Props aren't set yet!
|
|
233
258
|
throw std::runtime_error("${stateClassName}'s data doesn't contain any props!");
|
|
234
259
|
}
|
|
260
|
+
return props;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
void J${stateUpdaterName}::updateViewProps(jni::alias_ref<jni::JClass> /* class */,
|
|
264
|
+
jni::alias_ref<${JHybridTSpec}::JavaPart> javaView,
|
|
265
|
+
jni::alias_ref<JStateWrapper::javaobject> newState,
|
|
266
|
+
jni::alias_ref<JStateWrapper::javaobject> oldState) {
|
|
267
|
+
std::shared_ptr<${JHybridTSpec}> hybridView = javaView->get${JHybridTSpec}();
|
|
268
|
+
std::shared_ptr<const ${propsClassName}> newProps = getPropsFromStateWrapper(newState);
|
|
269
|
+
std::shared_ptr<const ${propsClassName}> oldProps = getPropsFromStateWrapper(oldState);
|
|
270
|
+
if (newProps == nullptr) [[unlikely]] {
|
|
271
|
+
throw std::runtime_error("Current StateWrapper doesn't contain any props!");
|
|
272
|
+
}
|
|
235
273
|
|
|
236
|
-
// Update
|
|
274
|
+
// Update only props that differ from the previous State snapshot.
|
|
237
275
|
${indent(propsUpdaterCalls.join('\n'), ' ')}
|
|
238
276
|
|
|
239
277
|
// Update hybridRef if it changed
|
|
240
|
-
if (
|
|
278
|
+
if (oldProps == nullptr
|
|
279
|
+
? newProps->hybridRef.isProvided()
|
|
280
|
+
: !newProps->hybridRef.hasSameValue(oldProps->hybridRef)) {
|
|
241
281
|
// hybridRef changed - call it with new this
|
|
242
|
-
const auto& maybeFunc =
|
|
282
|
+
const auto& maybeFunc = newProps->hybridRef.get();
|
|
243
283
|
if (maybeFunc.has_value()) {
|
|
244
284
|
maybeFunc.value()(hybridView);
|
|
245
285
|
}
|
|
246
|
-
props->hybridRef.isDirty = false;
|
|
247
286
|
}
|
|
248
287
|
}
|
|
249
288
|
|
|
@@ -26,8 +26,12 @@ export function createSwiftHybridViewManager(
|
|
|
26
26
|
const { HybridTSpec, HybridTSpecSwift, HybridTSpecCxx } = getHybridObjectName(
|
|
27
27
|
spec.name
|
|
28
28
|
)
|
|
29
|
-
const {
|
|
30
|
-
|
|
29
|
+
const {
|
|
30
|
+
component,
|
|
31
|
+
descriptorClassName,
|
|
32
|
+
propsClassName,
|
|
33
|
+
shadowNodeClassName,
|
|
34
|
+
} = getViewComponentNames(spec)
|
|
31
35
|
const implementation = spec.config.getIosAutolinkedImplementation(spec.name)
|
|
32
36
|
if (implementation?.language !== 'swift') {
|
|
33
37
|
throw new Error(
|
|
@@ -40,18 +44,18 @@ export function createSwiftHybridViewManager(
|
|
|
40
44
|
const setter = p.getSetterName('swift')
|
|
41
45
|
const bridge = new SwiftCxxBridgedType(p.type, false)
|
|
42
46
|
const parse = bridge.parseFromCppToSwift(
|
|
43
|
-
`newViewProps.${name}.
|
|
47
|
+
`newViewProps.${name}.get()`,
|
|
44
48
|
'c++'
|
|
45
49
|
)
|
|
46
50
|
return `
|
|
47
51
|
// ${p.jsSignature}
|
|
48
|
-
if (
|
|
52
|
+
if (oldViewProps == nullptr
|
|
53
|
+
? newViewProps.${name}.isProvided()
|
|
54
|
+
: !newViewProps.${name}.hasSameValue(oldViewProps->${name})) {
|
|
49
55
|
swiftPart.${setter}(${indent(parse, ' ')});
|
|
50
|
-
newViewProps.${name}.isDirty = false;
|
|
51
56
|
}
|
|
52
57
|
`.trim()
|
|
53
58
|
})
|
|
54
|
-
|
|
55
59
|
const mmFile = `
|
|
56
60
|
${createFileMetadataString(`${component}.mm`)}
|
|
57
61
|
|
|
@@ -87,6 +91,7 @@ using namespace ${namespace}::views;
|
|
|
87
91
|
|
|
88
92
|
@implementation ${component} {
|
|
89
93
|
std::shared_ptr<${HybridTSpecSwift}> _hybridView;
|
|
94
|
+
BOOL _didDropView;
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
+ (void) load {
|
|
@@ -100,6 +105,7 @@ using namespace ${namespace}::views;
|
|
|
100
105
|
|
|
101
106
|
- (instancetype) init {
|
|
102
107
|
if (self = [super init]) {
|
|
108
|
+
_props = ${shadowNodeClassName}::defaultSharedProps();
|
|
103
109
|
std::shared_ptr<${HybridTSpec}> hybridView = ${getHybridObjectConstructorCall(spec.name)}
|
|
104
110
|
_hybridView = std::dynamic_pointer_cast<${HybridTSpecSwift}>(hybridView);
|
|
105
111
|
[self updateView];
|
|
@@ -119,31 +125,50 @@ using namespace ${namespace}::views;
|
|
|
119
125
|
[self setContentView:view];
|
|
120
126
|
}
|
|
121
127
|
|
|
128
|
+
- (void) notifyOnDropView {
|
|
129
|
+
// A recycled component can later be invalidated. Notify only once per mount.
|
|
130
|
+
if (_didDropView) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
${swiftNamespace}::${HybridTSpecCxx}& swiftPart = _hybridView->getSwiftPart();
|
|
134
|
+
swiftPart.onDropView();
|
|
135
|
+
_didDropView = YES;
|
|
136
|
+
}
|
|
137
|
+
|
|
122
138
|
- (void) updateProps:(const std::shared_ptr<const react::Props>&)props
|
|
123
139
|
oldProps:(const std::shared_ptr<const react::Props>&)oldProps {
|
|
140
|
+
// A props update marks a newly mounted or still-active component.
|
|
141
|
+
_didDropView = NO;
|
|
142
|
+
|
|
124
143
|
// 1. Downcast props
|
|
125
|
-
const auto&
|
|
126
|
-
auto
|
|
144
|
+
const auto& newViewProps = *std::static_pointer_cast<const ${propsClassName}>(props);
|
|
145
|
+
const auto* oldViewProps = static_cast<const ${propsClassName}*>(oldProps.get());
|
|
127
146
|
${swiftNamespace}::${HybridTSpecCxx}& swiftPart = _hybridView->getSwiftPart();
|
|
128
147
|
|
|
129
|
-
// 2. Update
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// hybridRef
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
148
|
+
// 2. Update only props that differ from the previous Props snapshot.
|
|
149
|
+
const bool hasTransactionPropChanges = oldViewProps == nullptr
|
|
150
|
+
? newViewProps.hasAnyProvidedProps()
|
|
151
|
+
: !newViewProps.hasSameProps(*oldViewProps);
|
|
152
|
+
if (hasTransactionPropChanges) {
|
|
153
|
+
swiftPart.beforeUpdate();
|
|
154
|
+
|
|
155
|
+
${indent(propAssignments.join('\n'), ' ')}
|
|
156
|
+
|
|
157
|
+
// Update hybridRef if it changed
|
|
158
|
+
if (oldViewProps == nullptr
|
|
159
|
+
? newViewProps.hybridRef.isProvided()
|
|
160
|
+
: !newViewProps.hybridRef.hasSameValue(oldViewProps->hybridRef)) {
|
|
161
|
+
// hybridRef changed - call it with new this
|
|
162
|
+
const auto& maybeFunc = newViewProps.hybridRef.get();
|
|
163
|
+
if (maybeFunc.has_value()) {
|
|
164
|
+
maybeFunc.value()(_hybridView);
|
|
165
|
+
}
|
|
142
166
|
}
|
|
143
|
-
|
|
167
|
+
|
|
168
|
+
swiftPart.afterUpdate();
|
|
144
169
|
}
|
|
145
170
|
|
|
146
|
-
//
|
|
171
|
+
// 3. Continue in base class
|
|
147
172
|
[super updateProps:props oldProps:oldProps];
|
|
148
173
|
}
|
|
149
174
|
|
|
@@ -152,6 +177,7 @@ using namespace ${namespace}::views;
|
|
|
152
177
|
}
|
|
153
178
|
|
|
154
179
|
- (void)prepareForRecycle {
|
|
180
|
+
[self notifyOnDropView];
|
|
155
181
|
[super prepareForRecycle];
|
|
156
182
|
${swiftNamespace}::${HybridTSpecCxx}& swiftPart = _hybridView->getSwiftPart();
|
|
157
183
|
swiftPart.maybePrepareForRecycle();
|
|
@@ -159,8 +185,7 @@ using namespace ${namespace}::views;
|
|
|
159
185
|
|
|
160
186
|
#ifdef ENABLE_RCT_COMPONENT_VIEW_INVALIDATE
|
|
161
187
|
- (void)invalidate {
|
|
162
|
-
|
|
163
|
-
swiftPart.onDropView();
|
|
188
|
+
[self notifyOnDropView];
|
|
164
189
|
[super invalidate];
|
|
165
190
|
}
|
|
166
191
|
#endif
|