Skip to content

Share gradient stop calculations with Kotlin Multiplatform - #58456

Open
kunal26das wants to merge 4 commits into
react:mainfrom
kunal26das:codex/kmp-gradient-pilot
Open

Share gradient stop calculations with Kotlin Multiplatform#58456
kunal26das wants to merge 4 commits into
react:mainfrom
kunal26das:codex/kmp-gradient-pilot

Conversation

@kunal26das

@kunal26das kunal26das commented Sep 10, 2026

Copy link
Copy Markdown

Summary

Native stacked review: Review this layer in the registered six-PR fork stack. This PR remains the upstream submission to react/react-native.

Share CSS gradient stop positioning and transition-hint calculations between Android and iOS using the Kotlin Multiplatform foundation in #58472. This is the first use case in a series; multipart framing #58474 and explicit scroll snap-target selection #58475 are separate proposals.

Review this layer: gradient implementation and first-consumer integration. The upstream diff includes the foundation and the independent RNTester test dependency fix in #58471 because cross-fork stacked bases are unsupported. Those dependencies can land independently; this layer changes 38 paths.

Android embeds the shared JVM code in its existing ReactAndroid AAR. iOS source builds opt in with RCT_USE_KMP=1 during CocoaPods installation. The common implementation takes stop positions and color-presence flags and returns positions, source color indices and interpolation weights. Native color interpolation, geometry, drawing and precision choices remain in the adapters. There is no Compose dependency or JavaScript API change.

This first consumer also establishes packaging and integration checks: actual Maven/npm consumers, shrinking, source applications, independent Kotlin framework coexistence, and repeatable cost probes. Catalyst keeps its native fallback. Full React Native core SwiftPM and published Apple prebuilt integration remain guarded; the separate XCFramework/SwiftPM consumer is a distribution experiment.

Feasibility and alternatives

The benefit is one Kotlin implementation and common tests for calculations otherwise maintained in Kotlin and Objective-C++. Android calls it directly without a new JNI interface. C++ is a strong alternative because the Apple adapter already uses C++ data types and React Native already ships a shared C++ runtime. This proposal does not establish that KMP is the preferred implementation.

The measured gradient bridge adds cost, including its conversions:

Probe Native → KMP
Five Apple simulator cases 2.5–43.2× latency; KMP 0.66–27.12 µs/call
Five Android emulator cases 1.58–2.96× latency; KMP 0.31–4.32 µs/call
Android allocated bytes Approximately 1.88–2.45×
Isolated Apple executable +687,736 bytes (~672 KiB)
Apple process footprint growth KMP 7.7–16.3 MiB in sampled runs

Each platform uses matched native/KMP inputs; hint inputs differ between platforms. These are simulator/emulator measurements, not app startup, frame time, installed size or physical-device results. Footprint snapshots are not allocation or leak measurements. Broader adoption needs an accepted performance budget and representative application/device evidence. Catalyst fallback also means the experiment does not eliminate every native copy.

Changelog:

[GENERAL] [CHANGED] - Share gradient stop calculations using Kotlin Multiplatform on Android and opt-in iOS source builds.

Test Plan

Base: upstream f109c0af8693b58e0716b77b0b7c0c2299fc0a0e. Local toolchain: JDK 17, Kotlin 2.4.20, Xcode 26.6/iOS 26.5. CI selects Xcode 26.4.1.

The common algorithms, native adapters and JVM packaging retain their previously validated implementations. Hosted-test runtime ownership is now established in this first consumer: full-pod sibling tests resolve shared classes through the actual application host, including projects without optional TestTargetID metadata. Standalone tests and tests hosted by a non-KMP app retain their own runtime. The application runner also rejects a second shared runtime in hosted test binaries.

Previously completed gradient-layer validation on unchanged algorithm and adapter sources, from packages/react-native/ReactShared:

./gradlew jvmTest iosSimulatorArm64Test exportAndroidJar --rerun-tasks --max-workers=2
./gradlew -PreactNativeSharedSmoke=true jvmTest iosSimulatorArm64Test --max-workers=2
./scripts/test-apple-smoke.sh
./scripts/test-apple-gradient.sh
RCT_KMP_BUILD_TYPE=Release ./scripts/test-apple-gradient.sh
  • 22 production common tests passed on each of JVM and ARM64 simulator. Opt-in smoke runs passed 26 each: the same 22 plus four compiler probes.
  • 521 real adapter parity cases passed in each Debug/Release × static/dynamic combination; Debug/Release Objective-C smoke passed five cases each.
  • Production JAR has no smoke class. npm dry-pack includes 34 shared-module files and excludes generated outputs/caches.

Fresh hosted-runtime review checks at 9e28d94bf595:

  • CocoaPods 1.14.2 and 1.15.2 each pass 104 generated configurations, with 208 per-SDK archive-link assertions plus host classification and repeated-hook checks. Cases include missing/stale metadata, custom app products, simulator/device host changes, and unrelated app dependencies. These are configuration checks, not 104 app builds.
  • The new binary guard rejects both retained gradient library/static products that contained a duplicate runtime in hosted integration tests.
  • Fresh full RNTester runs at this exact head pass in both static-library and static-framework modes: each executes 151 unit tests, with 16 upstream skips and zero failures under Address/Undefined Behavior Sanitizers, and launches successfully. These repeat the same suite. The app debug library is the sole shared-runtime owner; hosted IntegrationTests have no archive/runtime, while standalone UnitTests retain theirs. No duplicate RNS warnings or stale multipart/scroll classes occur. IntegrationTests has no enabled upstream test methods; the small fixtures below provide actual host/API-sharing test executions.
  • Exact-source small CocoaPods/Xcode projects pass five real XCTest cases in each static-library and static-framework mode on the ARM64 iOS simulator. The tests call the shared APIs and consume host-created objects. Metadata-present, metadata-free and SDK-dependent hosted bundles define no RNS classes; the app, standalone tests and tests hosted by a non-KMP app retain their expected runtime. Both modes have zero failures/skips or duplicate RNS warnings. Device/simulator host selection is checked against real Xcode settings; device execution is not claimed.
  • Architecture- or SDK-version-qualified host paths that cannot be resolved during pod installation remain a documented limitation; the helper does not treat them as proof of an app-provided runtime.

Previously executed on the identical production implementation, using the checked-in runners documented in ReactShared/README.md:

  • Android core: 611 tests/93 suites passed, including 12 gradient tests.
  • Three published and two packed-source AARs; fresh Maven and npm-source consumers passed Debug and R8 Release with artifact-provenance and class-uniqueness checks. Release adapter assertions passed on API 26 and 37.
  • ARM64 RNTester Release built; ten gradient routes exercised on each of API 26/37; representative rendering inspected; 16 KiB APK alignment passed.
  • All six Apple Debug/Release frameworks built, including Intel simulator slices.
  • Earlier gradient RNTester runs with libraries, static frameworks and dynamic frameworks each passed 151 tests, with 16 upstream skips and no failures. Review subsequently found duplicate shared classes in the library/static hosted test bundles despite those passes; these historical passes alone do not establish runtime ownership. The new artifact guard rejects both retained faulty products. Rendering checks were separate.
  • Unsigned iOS ARM64 Release and universal Catalyst builds passed; Catalyst's ARM64 app and native fallback were checked.
  • Independent Kotlin frameworks passed four static/dynamic ownership combinations, serial and concurrent calls.
  • Separate SwiftPM distribution consumer built for device, simulator and Catalyst; one ARM64 simulator test passed.
  • Shared build probes with warm dependencies/compiler and output cache disabled: clean 11.95 s, unchanged 0.53 s, source edit 7.91 s. Configuration-cache reuse and executed tests checked separately.

Intel simulator execution remains deferred; physical-device execution and complete React Native core SwiftPM/prebuilt adoption are outstanding. Hosted CI needs upstream approval; local passes do not stand in for that approval.

@meta-cla

meta-cla Bot commented Sep 10, 2026

Copy link
Copy Markdown

Hi @kunal26das!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

@meta-cla

meta-cla Bot commented Sep 10, 2026

Copy link
Copy Markdown

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 10, 2026
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Sep 10, 2026
@kunal26das kunal26das changed the title Share gradient stop calculations with Kotlin Multiplatform Introduce a Kotlin Multiplatform pilot for gradient calculations Sep 10, 2026
@kunal26das
kunal26das force-pushed the codex/kmp-gradient-pilot branch from 6b32111 to 597c840 Compare September 10, 2026 19:02
@kunal26das kunal26das changed the title Introduce a Kotlin Multiplatform pilot for gradient calculations Share gradient stop calculations with Kotlin Multiplatform Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant