-
Notifications
You must be signed in to change notification settings - Fork 24.3k
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
De-duplicate building Spannable
on Android
#39630
Conversation
@NickGerleman I would be extremely grateful if you helped me with reviewing this or finding somebody to review this 😊 |
Spannable
on Android
Base commit: 3d09b6f |
@mdvacca would you be able to take a look at this? It relates to removing duplication for Spannable generation. IDK what our unit test coverage looks like here. |
@NickGerleman Thanks! @mdvacca I would be grateful if you took a look at this! 🫶 Unfortunately, if I were to answer the question, it would be "not so good"; I assume that if such unit tests existed, they would be located in |
This comment was marked as off-topic.
This comment was marked as off-topic.
I can keep bumping. Let me know if there's anything I could do to move this forward |
I've been checking with @mdvacca offline. I think he was sick last week, but mentioned he was planning to take a look and give some feedback. Thank you for keeping us honest here. |
@mdvacca It would be awesome if you could give it a quick look before the end of the week. I could start working on eventual changes before the next round. |
Hi @cubuspl42, thanks for the PR and apologies for the slow response. |
This comment was marked as off-topic.
This comment was marked as off-topic.
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.
Hi @cubuspl42, thanks for working on this. It’s due for us to reduce the duplication of this code!
I wanted to share general feedback about the PR before getting into details:
- You are creating new public classes and interfaces. We recently created a new package called com.facebook.react.internal, where we are going to start moving public classes and interfaces that are part of the “core” but not part of the public API of React Native. I believe this is a good set of classes and interfaces that we can start including in there. Could you move these new classes and interfaces into “com.facebook.react.internal.view.text”?
- As part of the test plan you mention:
I tried to ensure that the refactored code is relatively easy to prove to be equivalent to the original duplicated one, but there's always a risk of a human mistake in this process. So far, I have been testing this by ensuring that nothing broke in the Text example section in RNTester.
Even if we have some tests for Text I don’t think they are enough for us to feel super comfortable about not breaking anything in production. The approach we usually take for these kind of refactors is running A/B test. From the PR standpoint, running an A/B test involves creating a feature flag in ReactFeatureFlags class and then refactor the PR to make sure we can gate the new changes. Can you add a new feature flag in ReactFeatureFlags and refactor your code to be able to A/B this refactor?
- You created this new class called “TextLayoutUtils” that contains several static methods that “add things to spannables or build spannables”. How about refactoring this code to use a builder pattern?
- All new public classes, interfaces and method must be documented
- OPTIONAL: We are starting to move the React Native codebase to Kotlin. This is not a must, but if you are very familiar with Kotlin it would be great to use kotlin in this refactor. Again this is not a hard requirement.
Thanks!
fa79677
to
6cceac3
Compare
I pushed a new revision, addressing most comments. What is left:
Most usages of the builder pattern I've encountered were a substitute for the Would you provide a few lines of code that show how things would look after the refactor so I'm sure we're on the same page?
This is going to be tricky, but it is probably possible. From the operational perspective, you're talking about A/B tests that Meta could run on the Facebook app and/or other Meta apps, do I get this right? |
1984899
to
f011eb0
Compare
@mdvacca Let me know what you think of the Kotlin "port" of the new bits. For example, I used a named object for utils, instead of exposing public functions, as for now some of the names were quite generic, like |
This comment was marked as off-topic.
This comment was marked as off-topic.
@mdvacca While I'm trying to take a humorous approach, this is a serious, externally sponsored effort, and we'll need multiple PRs to implement the desired functionality. I beg for some feedback loop fluency here. I'll start figuring out the code for the feature flags, but feedback on rewriting everything to Kotlin will also be extremely helpful. Please let me know if I, or Expensify, can do anything to improve the feedback loop situation. |
@cubuspl42, will take a look at it today, thank you! |
@mdvacca Thank you! |
Correct, technically this would mean to add a new static variable in ReactFeatureFlags and then refactor the PR to be able to execute "old Code" or "new code" based on the value of the feature flag. We will take care of running the experiment at Meta and analyzing result of it. I will share some detail of the code in a bit |
Thanks for moving the new classes to internal and migrating them to Kotlin!
I did a further analysis and I think that using a builder pattern will require a much bigger refactor, I think we could leave it like this for now. Although it will important to add a FeatureFlag as I mentioned in my comment above. Also, let's document all new public classes, interfaces and method using KDoc @cortinico, can you also take a quick look at this PR? in particular the Kotlin side Thanks! |
new SetSpanOperation( | ||
start, end, new CustomLineHeightSpan(textAttributes.getEffectiveLineHeight()))); | ||
} | ||
final var textFragmentList = new BridgeTextFragmentList(fragments); |
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.
Let's say you create a feature flag called ReactFeatureFlags.enableSpannableCreationUnification, then here's one of the places where you have to gate your new code:
if (enableSpannableCreationUnification) {
textFragmentList = new BridgeTextFragmentList(fragments);
...
} else {
for (int i = 0, length = fragments.size(); i < length; i++) {
....
}
}
I know this make this code a bit dirty, but it would be important to understand the impact of this PR.
We will cleanup the code after we confirm that this PR works as expected.
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.
Done
If I'm not mistaken, I added KDocs to all new classes. Are you sure about the methods? Should I add comment docs for all Java getters, Kotlin properties, the new |
f011eb0
to
c0fcf72
Compare
It would be nice, we are also exploring doing a V7 for lottie to address this and the new install_module script on iOS (since it only supports RN 70 and above). But it would be nice to not have to worry about this too much 🙏🏻 |
I'm not an authoritative source here, but I'm 90% sure this is now considered a public API: Line 20 in 7bcdb23
I'd use this on your place as a fix for the specific problem you encountered. |
@cubuspl42 sounds good! i'll update the patch accordingly. thanks for the great hint. |
For future reference, there's one other local definition of Line 33 in 714b502
If it ever needs extracting, we could name it I don't think we have any cases of I think we don't need more future predicting here. |
…1-a58ec074b (#26587) # Why react-native nightlies testing is broken on 0.74.0-nightly-20240121-a58ec074b # How - [core] CallInvokerHolderImpl requires a framework api: facebook/react-native#42399 - [lottie-react-native] breaking change discussed at: facebook/react-native#39630 (comment)
@NickGerleman Kind bump on the experiment info What's the ETA for starting and finishing it? |
Because of the nature of needing to incrementally roll out to production audience, and get feedback, it will be a while before we are able to remove the forking (potentially a month or more). But it should be possible to continue building on top of it in the meantime. |
@NickGerleman Thanks! As I understand it, it's already live, and the finishing ETA is roughly 1-3 months. I don't mean to push it, I just want to establish a timeline. |
Summary: `TextLayoutUtils`: Use named arguments to ensure same-type arguments (like `start`/`end`) are not confused This is a minor readability follow-up to #39630. ## Changelog: <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: [ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests --> [INTERNAL] [CHANGED] - Increase the `TextLayoutUtils` readability slightly Pull Request resolved: #42593 Reviewed By: NickGerleman Differential Revision: D53028402 Pulled By: mdvacca fbshipit-source-id: 39e99ba70b93eecfc51bda19d30a5b1977cfe406
Hey @cubuspl42, while rolling this out to our users, we discovered it causes a performance regression, in the time to render content, for a couple of our scenarios. More concretely, a number of users in the millions would go from "responsive" to "unresponsive" on those surfaces. I have deallocated the experiment due to performance impact. If we wanted to try shipping this again, I think we'd need to understand where this issue presents in real apps, and come up with a fix. |
Thank you for sharing these results! I must admit that they surprised me, but prove you had a good intuition to set up an experiment. Did the mentioned user group share a common configuration of React Native? Was it Fabric/bridgeless? |
The surfaces were Fabric + Bridgeless. One involved a scrollable grid of components with text. One was more static. Both in an app with very large usage. A lot of real-world Android devices are low/middle-end, which means sometimes a very minor change on higher end device, can push a surprisingly large number of real-world devices from one responsiveness threshold to another. |
It was brought to my attention that someone from Meta (I'm not sure whether it was you personally) contacted Expensify, informing that Meta will no longer accept big PRs related to
Thank you for your help so far; I'm sorry it ended this way. |
Hey there. I think @TheSavior chatted with folks at Expensify wrt Text changes. Our own problem right now, is that certain classes of contributions are hard to land, from outside of Meta. In these cases, where we have encouraged contribution, we've often created a bad experience for change authors (hard/slow to merge, hard to debug regressions effecting product code), coupled with at times a spikey workload for engineers at Meta, who usually don't have time budgeted ahead of time for larger changes outside their current area of focus. I don't know that we ever explicitly chatted about what to do with in-flight changes. If there is a scoped, logical wrap-up, to changes already in flight, I think we could take a look at those. But if we think this is more likely to become dead code, I do really appreciate the offer to help clean things up. I really appreciate all the time you spent here, working to make RN better. I am sorry, we couldn't offer a better experience here. |
I do think it is a reasonable question of whether there is something that could be contributed around testing to increase our confidence in merging changes to Text. @NickGerleman do you have any thoughts there? |
Meta has collectively spent some tens of thousand of hours on tooling and infra, but has usually focused on using them for Meta products, or the Meta monorepo. That puts us into a complicated state, where we do use this tooling, as a pragmatic way to ensure RN quality (for our apps, and OSS). But those tools don't come free to OSS. Without commenting about what can be contributed vs not, there are a few classes of tooling that come to mind, that we use to ensure quality: Fast and stable screenshot comparison testingWe get feedback on whether a change visually effects both our real-world surfaces, and fixtures we created. E.g. when I was working on the Samsung TextInput SEV a while back, I set up infra to visually compare a lot of interesting formatting span/nesting scenarios, for both Fabric and Paper (and they have since caught real regressions). There have been some efforts to stand up OSS E2E infra before, but they have run into stability issues, and never got around to a lot of what is really useful (e.g. screenshot comparison). If we had reliable, simple infra, to make it easy for contributors to add image baselines, that would give us a lot more confidence for some changes. Experimentation infraWe often test by observing differences in behavior/performance when a change is enabled vs not. This is inherently something that has to come from a collection of product code (e.g. when I was at another company, we did the same thing). That looks like, enabling a change for a large fleet of users, across surfaces, and having good enough metrics to understand if we see e.g. more rage shakes, crashes, perf changes, impression changes, etc. Perf infraI know things like the Java sampling profiler work pretty out of the box in OSS (and I think, that is probably the best place to start looking for diagnosing a perf issue for e.g. a change like this). But there is often bespoke infa Meta has to make this sort of thing easier and faster. E.g. If I see that a change makes a surface slower to start, I can go look at how much time is spent, via a sampling profiler, that ran during the duration, collecting information across real production users. Or, tooling for e.g. trace markers, which I'm not sure are wired to sinks in most OSS builds (I remember when I was at MSFT, we did wire them to Windows ETW). |
To clarify though, I don't think better tooling is a magic bullet to ensure smooth contributions, though for many cases, I think it does help build confidence. I think another component, is aligning changes which will require a lot of interaction from engineers at Meta, to work the engineer is chartered to. That helps make it a lot easier to prioritize the work, and for someone to represent it or deal with any fallout when shipping it. I can say personally, at any given moment, there will be something falling off my plate that someone has asked me to do, just by the nature of the scale factor of the relatively small number of folks working on RN, compared to the very large userbase. So, it's easier to keep things above that line, when we can very easily justify the work. |
These are extremely helpful thoughts; this is a communication breakthrough from my perspective. I hope I'm not stepping out of the line here, but this has been bothering me: Would it be possible for you to talk with your superiors and discuss Meta's priorities in the context of React Native within its Open Source umbrella project? Would be possible to establish a budget for this "communitization" effort of React Native? A few talented engineers from the Expensify project (and the supporting companies, like Software Mansion) would love and be able to help with this, but I'm not going to lie; these are very difficult technical tasks requiring expert knowledge, experience and time. The budget established for implementing spans (eventually, inline code blocks) in |
Summary: This removes the bulk of code added in facebook#39630. We're not shipping it, as it caused performance regressions. After this, I'm going to see if I can delete the non-MapBuffer version of TextLayoutManager, which is probably Differential Revision: D56796936
Summary: This removes the bulk of code added in facebook#39630. We're not shipping it, as it caused performance regressions. After this, I'm going to see if I can delete the non-MapBuffer version of TextLayoutManager, which is probably Differential Revision: D56796936
Summary: This removes the bulk of code added in facebook#39630. We're not shipping it, as it caused performance regressions. After this, I'm going to see if I can delete the non-MapBuffer version of TextLayoutManager, which is probably Differential Revision: D56796936
Summary: This removes the bulk of code added in facebook#39630. We're not shipping it, as it caused performance regressions. Changelog: [Internal] Differential Revision: D56796936
Summary: This removes the bulk of code added in #39630. We're not shipping it, as it caused performance regressions. Changelog: [Internal] Reviewed By: christophpurrer Differential Revision: D56796936 fbshipit-source-id: 82f3a51cf145bc1695d70393e1f050685a1e6174
Summary: This removes the bulk of code added in facebook#39630. We're not shipping it, as it caused performance regressions. Changelog: [Internal] Reviewed By: christophpurrer Differential Revision: D56796936 fbshipit-source-id: 82f3a51cf145bc1695d70393e1f050685a1e6174
Summary: This removes the bulk of code added in facebook#39630. We're not shipping it, as it caused performance regressions. Changelog: [Internal] Reviewed By: christophpurrer Differential Revision: D56796936 fbshipit-source-id: 82f3a51cf145bc1695d70393e1f050685a1e6174
The facebook/react-native Android Test Suite was disabled for components that rely on Yoga for layout (commit 709a441). I don't believe Yoga layout is essential to run tests for Text and TextInput.
More info in PR #35949 and more useful code pointers in comment #35130 (comment). |
Summary:
A first step in my work on react-native-community/discussions-and-proposals#695
De-duplicate the code for creating
Spannable
on Android. I'm planning to add quite serious new features to this module. This would be really hard with the current level of code duplication.Changelog:
[INTERNAL] [CHANGED] - De-duplicate building
Spannable
on AndroidTest Plan:
I tried to ensure that the refactored code is relatively easy to prove to be equivalent to the original duplicated one, but there's always a risk of a human mistake in this process. So far, I have been testing this by ensuring that nothing broke in the
Text
example section in RNTester.