-
Notifications
You must be signed in to change notification settings - Fork 11
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
iOS Fabric implementation #79
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see one 'in review' on DOC-1863, one 'unstarted', and one 'design approved'. I still have some open questions from a few weeks ago, and I think @jth0024 does as well, that need to be answered before we merge this approach (probably we should add Jordan as a reviewer on DOC-1863, also). I think it is possible that we can make this design work, but I'm not sure we're ready to commit to it based on the state of the design doc?
So I guess -1 for now on that front (and certainly there are style / functionality changes that are currently needed), but I don't fundamentally object to this design if we can show that it works well.
@@ -216,16 +216,61 @@ - (void)log:(double)logLevel message:(NSString *)message { | |||
|
|||
static const char *_rctview_previous_attributes_key = "associated_object_rctview_previous_attributes_key"; | |||
|
|||
static void set_fsClass(id json, RCTView *view) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be more idiomatic to have this as an extension on RCTView
(i.e., -[RCTView _fs_set_fsClass:]
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a static function is the correct idiom to use when the functionality's use is limited to the current file. Extensions are meant to allow use across files - they add functions to the lookup table, and carry the risk of being referenced outside of their intended use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, fair enough. (Generally I tend to prefix static methods with _
, and I tend to prefer the parameter order object_verb_noun(object, parameters...)
, which in this case would translate to _rctview_set_fsClass(RCTView *view, id json)
, just to scope things a little more tightly -- like we do two lines prior, scoping _rctview_previous_attributes_key
by name -- but that could be a matter of taste.)
src/index.ts
Outdated
interface NativeCommands { | ||
fsClass: (viewRef: React.ElementRef<FSComponentType>, fsClass: string) => void; | ||
fsAttribute: (viewRef: React.ElementRef<FSComponentType>, fsAttribute: object) => void; | ||
fsTagName: (viewRef: React.ElementRef<FSComponentType>, fsTagName: string) => void; | ||
dataElement: (viewRef: React.ElementRef<FSComponentType>, dataElement: string) => void; | ||
dataSourceFile: (viewRef: React.ElementRef<FSComponentType>, dataElement: string) => void; | ||
dataComponent: (viewRef: React.ElementRef<FSComponentType>, dataElement: string) => void; | ||
} | ||
|
||
const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({ | ||
supportedCommands: [ | ||
'fsClass', | ||
'fsAttribute', | ||
'fsTagName', | ||
'dataElement', | ||
'dataComponent', | ||
'dataSourceFile', | ||
], | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned in the design doc, I'm still a little nervous about doing these sequentially. Do we know what thread JavaScript runs on? I guess it might be okay iff a view is added to the hierarchy only after our applyFSPropertiesWithRef
completes. But if that's the case we need to document that, ideally, in a way that shows why that is the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe your concerns were addressed in the discussion and design doc. I don't think it's appropriate to write an essay about React Native's architecture here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, my concerns about threading of this were addressed in the design doc. It might be worth adding a comment to, if nothing else, point to the discussion in the design doc for why this is ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment added
src/index.ts
Outdated
// @ts-expect-error `currentProps` is missing in `NativeMethods` | ||
const fsClass = element.currentProps['fsClass']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we could DRY this currentProps is missing
thing by pulling currentProps
into a local. And broadly I wonder if we could DRY it by just iterating all of the supportedCommands
or something rather than naming each one three times...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked at this and as far as I can tell, it's not completely trivial. Interface properties are not iterable and only exist at compile time unless customers implement custom transformers. Plugin support for custom transformers continues to be an open issue after five years. This aesthetic issue should not be a blocking concern for this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, big oof that we can't iterate over an interface.
I was kind of thinking something like (pseudocode):
for (prop in ['fsClass', 'fsAttribute', 'fsTagName', ...]) {
// @ts-expect-error ...
const val = element.currentProps[prop];
if (val) {
Commands[prop](element, val); /* will we need a ts waiver here? */
}
}
Something like this definitely has the chance to grow copy-and-paste errors if it needs to be touched later so if we can avoid repeating something six times, that might be nice. If we were feeling extremely fancy, that list (['fsClass', ...]
) could also be a const
elsewhere that we could pass as the parameter to codegenNativeCommands
, for one more repetition eliminated!
Or, if nothing else, hoisting out const props = element.currentProps;
, so we only have to @ts-expect-error
once. What do you think about these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hoisted the element.currentProps
so we only have the ts-expect-error
once.
Improved types so copy-and-paste errors will be unlikely since it has to match the NativeCommand
type.
Sorry @jwise , I should've labeled this PR as work in progress. I wanted to get my |
2301cc4
to
e541035
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the swizzles look good to me. some various small comments, but LGTM from my perspective.
// we separately swizzle each class manually | ||
#pragma clang diagnostic push | ||
#pragma clang diagnostic ignored "-Wundeclared-selector" | ||
SWIZZLE_HANDLE_COMMAND(RCTViewComponentView); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i like this, super easy to understand what the swizzles are.
if (existingMethod) { | ||
IMP existingImplementation = method_getImplementation(existingMethod); | ||
if (existingImplementation != swizzledViewComponentViewCommandImplementation) { | ||
NSAssert(strncmp(className, "RCT", 3) != 0, @"React Native framework class %s needs handleCommand support! Please contact FullStory support with this message.", className); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm not the most familiar with NSAsserts really - does this quit the app like a crash? I guess like, if a developer ends up in this block, would they be able to still use their app in debug mode? it would kinda suck if a customer has to update their RN fullstory version in the middle of a development session because we're NSAsserting something here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it would crash the app when it is running in debug mode. I agree that this would be very disruptive to the developer. However, it's kind of unsafe for them to use the SDK if this is the case. If we are worried that this assert will be triggered, we could instead swizzle all subclasses per the original design. Or, we could add a terminate-with-error-message method to the SDK in a followup PR and terminate with this error message instead of NSAssert.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok with this being a crashing NSAssert -- especially because a developer wanting to work around it doesn't need to wait for us to release a new plugin version, but instead can make the modification themselves in their local tree. Nice work on checking this in debug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ObjC changes LGTM. I suggested a few comments and a few style changes, and it would be nice to see something to shore up the DRY situation down there in TS land, but I trust you to make those changes as appropriate. Looks like an approach that is going to work out!
Co-authored-by: Joshua Wise <joshua@joshuawise.com>
Co-authored-by: Joshua Wise <joshua@joshuawise.com>
Co-authored-by: Joshua Wise <joshua@joshuawise.com>
See design doc
This work seeks to solve an issue where props are statically defined in Fabric and cannot set our FullStory props onto views.