You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit aa811de
Browse filesBrowse the repository at this point in the historyBrowse files
Scope the TextInput spannable cache to the React instance
Summary:
WARNING: Generated by Autopilot (alpha) — review carefully, verify the underlying claim before accepting.
Agent: React Native Agent (Bugs) | Trajectory: https://www.internalfb.com/intern/devai/devmate/inspector/4f3686f6-6eac-416a-8116-4fd4ee5a627f/ | SC job: https://www.internalfb.com/intern/sandcastle/instance/54043196239422512/
---
On Fabric, `ReactEditText` caches its spannable in `TextLayoutManager` keyed by react tag, and the shadow node's state keeps that tag as `cachedAttributedStringId` to re-measure against. The cache was a single process-global map keyed only by tag. React tags restart for every new React instance, so after a reload the old and new instances can use the same tags in the same map: the new instance can read the old instance's spannable, and when an old `ReactEditText` is finalized it removes the new view's entry. The next cached measure then fails `checkNotNull` in `getOrCreateSpannableForText` with "Required value was null".
This change gives each React instance its own spannable cache:
- `TextLayoutManager` keeps one map per `ReactApplicationContext`. `FabricUIManager` creates it in its constructor and removes it in `invalidate()`.
- `FabricUIManager.measureText` passes its own context, so a cached measure only looks in that instance's map.
- `ReactEditText` writes to and evicts from the map of the context it was created with (`ThemedReactContext.reactApplicationContext`). Create, destroy, read and write all resolve the key through one helper, so a view's `ThemedReactContext` and `FabricUIManager`'s `ReactApplicationContext` always hit the same map. After teardown, an old view's set or evict does nothing because its map is gone.
- A layout that started before `invalidate()` can still reach `measureText` after the map is removed. In that case the cached measure uses an empty spannable instead of throwing, because the cache-id `MapBuffer` carries only the id and there is nothing to rebuild from. The result belongs to an instance that is going away. A missing entry in a live map still fails `checkNotNull`.
This replaces the previous version of this diff, where `finalize()` only evicted the spannable the instance itself had cached. Per-tag eviction still happens in `finalize()`, but now only within the view's own instance, where tags are never reused. I did not move eviction to `onDropViewInstance`: a commit whose layout is still running on a background thread can measure a TextInput after the UI thread has already processed that view's delete, so evicting right away would add a new way to hit the same `checkNotNull`.
Changelog:
[Android][Fixed] - Fix TextInput measurement crash ("Required value was null") after a React instance reload, by scoping the cached TextInput spannables to the React instance
Reviewed By: zeyap
Differential Revision: D122870300
0 commit comments