Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: facebook/react-native
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.77.0-rc.2
Choose a base ref
...
head repository: facebook/react-native
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.77.0-rc.4
Choose a head ref
  • 14 commits
  • 53 files changed
  • 8 contributors

Commits on Dec 10, 2024

  1. Update Podfile.lock

    Changelog: [Internal]
    blakef committed Dec 10, 2024
    Configuration menu
    Copy the full SHA
    77b9c55 View commit details
    Browse the repository at this point in the history

Commits on Dec 16, 2024

  1. Disable weak event emitter in AttributedString for Mac Catalyst (#48225)

    Summary:
    Pull Request resolved: #48225
    
    Fixes #47762
    
    The weak event emitter in AttributedString attributes is causing a serialization error when typing into a TextInput in a Mac Catalyst build. We can resolve this by not putting the event emitters in the attributed string, but this is likely to cause other issues with event handling for nested <Text> components.
    
    ## Changelog
    
    [iOS][Fixed] - Workaround for Mac Catalyst TextInput crash due to serialization attempt of WeakEventEmitter
    
    Reviewed By: NickGerleman
    
    Differential Revision: D66664583
    
    fbshipit-source-id: efdfbcb0db4d5e6b9bf7c14f9bbb221faae2d724
    rozele authored and cipolleschi committed Dec 16, 2024
    Configuration menu
    Copy the full SHA
    840382d View commit details
    Browse the repository at this point in the history
  2. Enable hermes debugger by configuration type instead of configuration…

    … name (#48174)
    
    Summary:
    Fixes an [issue](#48168) where only iOS configurations with "Debug" in the name are configured to use the hermes debugger.
    
    ## Changelog:
    
    <!-- Help reviewers and the release process by writing your own changelog entry.
    
    Pick one each for the category and type tags:
    
    For more details, see:
    https://reactnative.dev/contributing/changelogs-in-pull-requests
    -->
    
    [IOS] [FIXED] - Enable hermes debugger by configuration type instead of configuration name
    
    Pull Request resolved: #48174
    
    Test Plan:
    Added new test scenarios that all pass:
    ```
    ruby -Itest packages/react-native/scripts/cocoapods/__tests__/utils-test.rb
    Loaded suite packages/react-native/scripts/cocoapods/__tests__/utils-test
    Started
    Finished in 0.336047 seconds.
    -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    56 tests, 149 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
    100% passed
    -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    166.64 tests/s, 443.39 assertions/s
    ```
    
    In a personal project with the following configurations:
    ```
    project 'ReactNativeProject', {
        'Local' => :debug,
        'Development' => :release,
        'Staging' => :release,
        'Production' => :release,
      }
    ```
    I added the following to my Podfile:
    ```
    installer.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            puts "#{config.name} is debug? #{config.type == :debug}"
        end
    end
    ```
    To confirm that my logic is correct:
    ```
    Local is debug? true
    Development is debug? false
    Staging is debug? false
    Production is debug? false
    ```
    
    Reviewed By: robhogan
    
    Differential Revision: D66962860
    
    Pulled By: cipolleschi
    
    fbshipit-source-id: 7bd920e123c9064c8a1b5d45df546ff5d2a7d8be
    benhandanyan authored and cipolleschi committed Dec 16, 2024
    Configuration menu
    Copy the full SHA
    b153ec8 View commit details
    Browse the repository at this point in the history
  3. Skip hidden folders when looking for third party components (#48182)

    Summary:
    Pull Request resolved: #48182
    
    Maintainers from SVG reached out because of an edge case they inencountered when generating the ComponentProvider. In their setup, they had a `.git` folder in the repo and the algorithm was spending a lot of time crawling the git folder.
    
    In general, we should avoid crawling hidden folders.
    
    This change fix that.
    
    ## Changelog:
    [General][Fixed] - Skip hidden folders when looking for third party components.
    
    Reviewed By: javache
    
    Differential Revision: D66959345
    
    fbshipit-source-id: 992a79f3cff22cd6a459e0272c8140bc329888da
    cipolleschi committed Dec 16, 2024
    Configuration menu
    Copy the full SHA
    621f13f View commit details
    Browse the repository at this point in the history
  4. Fix handling removal of transitioning views (#47634)

    Summary:
    Related PR in `react-native-screens`:
    
    * software-mansion/react-native-screens#2495
    
    Additional context:
       * [my detailed explanation of **one of the issues**](software-mansion/react-native-screens#2495 (comment))
       * [Android Developer: ViewGroup.startViewTransition docs](https://developer.android.com/reference/android/view/ViewGroup#startViewTransition(android.view.View))
    
    On Android view groups can be marked as "transitioning" with a `ViewGroup.startViewTransition` call. This effectively ensures, that in case a view group is marked with this call and its children are removed, they will be still drawn until `endViewTransition` is not called.
    
    This mechanism is implemented in Android by [keeping track of "transitioning" children in auxiliary `mTransitioningViews` array](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewGroup.java#7178). Then when such "transitioning" child is removed, [it is removed from children array](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewGroup.java#5595) but it's [parent-child relationship is not cleared](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewGroup.java#5397) and it is still retained in the auxiliary array.
    
    Having that established we can proceed with problem description.
    
    https://github.com/user-attachments/assets/d0356bf5-2f17-4b06-ba53-bfca659a1071
    
    <details>
    <summary>Full code</summary>
    
    ```javascript
    import { NavigationContainer } from 'react-navigation/native';
    import React from 'react';
    import { createNativeStackNavigator } from 'react-navigation/native-stack';
    import { enableScreens } from 'react-native-screens';
    import {
      StyleSheet,
      Text,
      View,
      FlatList,
      Button,
      ViewProps,
      Image,
      FlatListProps,
      findNodeHandle,
    } from 'react-native';
    
    enableScreens(true);
    
    function Item({ children, ...props }: ViewProps) {
      return (
        <View style={styles.item} {...props}>
          <Image source={require('../assets/trees.jpg')} style={styles.image} />
          <Text style={styles.text}>{children}</Text>
        </View>
      );
    }
    
    function Home({ navigation }: any) {
      return (
        <View style={styles.container}>
          <Button title="Go to List" onPress={() => navigation.navigate('List')} />
        </View>
      );
    }
    
    function ListScreenSimplified({secondVisible}: {secondVisible?: (visible: boolean) => void}) {
      const containerRef = React.useRef<View>(null);
      const innerViewRef = React.useRef<View>(null);
      const childViewRef = React.useRef<View>(null);
    
      React.useEffect(() => {
        if (containerRef.current != null) {
          const tag = findNodeHandle(containerRef.current);
          console.log(`Container has tag [${tag}]`);
        }
        if (innerViewRef.current != null) {
          const tag = findNodeHandle(innerViewRef.current);
          console.log(`InnerView has tag [${tag}]`);
        }
        if (childViewRef.current != null) {
          const tag = findNodeHandle(childViewRef.current);
          console.log(`ChildView has tag [${tag}]`);
        }
      }, [containerRef.current, innerViewRef.current, childViewRef.current]);
    
      return (
        <View
          ref={containerRef}
          style={{ flex: 1, backgroundColor: 'slateblue', overflow: 'hidden' }}
          removeClippedSubviews={false}>
          <View ref={innerViewRef} removeClippedSubviews style={{ height: '100%' }}>
            <View ref={childViewRef} style={{ backgroundColor: 'pink', width: '100%', height: 50 }} removeClippedSubviews={false}>
              {secondVisible && (<Button title='Hide second' onPress={() => secondVisible(false)} />)}
            </View>
          </View>
        </View>
      );
    }
    
    function ParentFlatlist(props: Partial<FlatListProps<number>>) {
      return (
        <FlatList
          data={Array.from({ length: 30 }).fill(0) as number[]}
          renderItem={({ index }) => {
            if (index === 10) {
              return <NestedFlatlist key={index} />;
            } else if (index === 15) {
              return <ExtraNestedFlatlist key={index} />;
            } else if (index === 20) {
              return <NestedFlatlist key={index} horizontal />;
            } else if (index === 25) {
              return <ExtraNestedFlatlist key={index} horizontal />;
            } else {
              return <Item key={index}>List item {index + 1}</Item>;
            }
          }}
          {...props}
        />
      );
    }
    
    function NestedFlatlist(props: Partial<FlatListProps<number>>) {
      return (
        <FlatList
          style={[styles.nestedList, props.style]}
          data={Array.from({ length: 10 }).fill(0) as number[]}
          renderItem={({ index }) => (
            <Item key={'nested' + index}>Nested list item {index + 1}</Item>
          )}
          {...props}
        />
      );
    }
    
    function ExtraNestedFlatlist(props: Partial<FlatListProps<number>>) {
      return (
        <FlatList
          style={styles.nestedList}
          data={Array.from({ length: 10 }).fill(0) as number[]}
          renderItem={({ index }) =>
            index === 4 ? (
              <NestedFlatlist key={index} style={{ backgroundColor: '#d24729' }} />
            ) : (
              <Item key={'nested' + index}>Nested list item {index + 1}</Item>
            )
          }
          {...props}
        />
      );
    }
    
    const Stack = createNativeStackNavigator();
    
    export default function App(): React.JSX.Element {
      return (
        <NavigationContainer>
          <Stack.Navigator screenOptions={{ animation: 'slide_from_right' }}>
            <Stack.Screen name="Home" component={Home} />
            <Stack.Screen name="List" component={ListScreenSimplified}/>
          </Stack.Navigator>
        </NavigationContainer>
      );
    }
    
    export function AppSimple(): React.JSX.Element {
      const [secondVisible, setSecondVisible] = React.useState(false);
    
      return (
        <View style={{ flex: 1, backgroundColor: 'lightsalmon' }}>
          {!secondVisible && (
            <View style={{ flex: 1, backgroundColor: 'lightblue' }} >
              <Button title='Show second' onPress={() => setSecondVisible(true)} />
            </View>
          )}
          {secondVisible && (
            <ListScreenSimplified secondVisible={setSecondVisible} />
          )}
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
      },
      nestedList: {
        backgroundColor: '#FFA07A',
      },
      item: {
        flexDirection: 'row',
        alignItems: 'center',
        padding: 10,
        gap: 10,
      },
      text: {
        fontSize: 24,
        fontWeight: 'bold',
        color: 'black',
      },
      image: {
        width: 50,
        height: 50,
      },
    });
    
    ```
    
    </details>
    
    Explanation (copied from [here](software-mansion/react-native-screens#2495 (comment))):
    
    I've debugged this for a while now & I have good understanding of what's going on. This bug is caused by our usage of `startViewTransition` and its implications. We use it well, however React does not account for case that some view might be in transition. Error mechanism is as follows:
    
    1. Let's have initially simple stack with two screens: "A, B". This is component rendered under "B":
    
    ```javascript
        <View //<-- ContainerView (CV)
          removeClippedSubviews={false}
          style={{ flex: 1, backgroundColor: 'slateblue', overflow: 'hidden' }}>
          <View removeClippedSubviews style={{ height: '100%' }}> // <--- IntermediateView (IV)
            <View removeClippedSubviews={false} style={{ backgroundColor: 'pink', width: '100%', height: 50 }} /> // <--- ChildView (ChV)
          </View>
        </View>
    ```
    
    2. We press the back button.
    3. We're on Fabric, therefore subtree of B gets destroyed before B itself is unmounted -> in our commit hook we detect that the screen B will be unmounted & we mark every node under B as transitioning by calling `startViewTransition`.
    4. React Mounting stage starts, view hierarchy is disassembled in bottom-up fashion (leafs first).
    5. ReactViewGroupManager receives MountItem to detach ChV from IV.
    6. A call to [`IV.removeView(ChV)` is made](https://github.com/facebook/react-native/blob/9c11d7ca68c5c62ab7bab9919161d8417e96b28b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt#L58-L73), which effectively removes ChV from `IV.children`, ***HOWEVER*** it does not clear `ChV.parent`, meaning that after the call, `ChV.parent == IV`. This happens, due to view being marked as in-transition by our call to `startViewTransition`. If the view is not marked as in-transition this parent-child relationship is removed.
    7. IV has `removeClippedSubviews` enabled, therefore a [call to `IV.removeViewWithSubviewsClippingEnabled(ChV)` is made](https://github.com/facebook/react-native/blob/9c11d7ca68c5c62ab7bab9919161d8417e96b28b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt#L68). [This function](https://github.com/facebook/react-native/blob/9c11d7ca68c5c62ab7bab9919161d8417e96b28b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java#L726-L744) does effectively two things:
        1. if the ChV has parent (interpretation: it has not yet been detached from parent), we compute it's index in `IV.children` (Android.ViewGroup's state) and remove it from the array,
        2. remove the ChV from `mAllChildren` array (this is state maintained by ReactViewGroup for purposes of implementing the "subview clipping" mechanism".
    
    The crash happens in 7.1, because ChV has been removed from `IV.children` in step 6, but the parent-child relationship has not been broken up there. Under usual circumstances (this is my hypothesis now, yet unconfirmed) 7.1 does not execute, because `ChV.parent` is nulled in step no. 6.
    
    Transitions. On Fabric, when some subtree is unmounted, views in the subtree are unmounted in bottom-up order. This leads to uncomfortable situation, where our components (react-native-screens), who want to drive & manage transitions are notified that their children will be removed after the subtrees mounted in screen subviews are already disassembled. **If we start animation in this very moment we will have staggering effect of white flash** [(issue)](software-mansion/react-native-screens#1685) (we animate just the screen with white background without it's children). This was not a problem on Paper, because the order of subtree disassembling was opposite - top-down. While we've managed to workaround this issue on Fabric using `MountingTransactionObserving` protocol on iOS and a commit hook on Android (we can inspect mutations in incoming transaction before it starts being applied) we still need to prevent view hierarchy from being disassembled in the middle of transition (on Paper this has also been less of an issue) - and this is where `startViewTransition` comes in. It allows us to draw views throughout transition after React Native removes them from HostTree model. On iOS we exchange subtree for its snapshot for transition time, however this approach isn't feasible on Android, because [snapshots do not capture shadows](https://stackoverflow.com/questions/42212600/android-screenshot-of-view-with-shadow).
    
    [Android does not expose a method to verify whether a view is in transition](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewGroup.java#7162) (it has `package` visibility), therefore we need to retrieve this information with some workaround. I see two posibilities:
    
    * first approach would be to override `startViewTransition` & `endViewTransition` in ReactViewGroup and keep the state on whether the view is transitioning there,
    * second possible approach would be as follows: we can check for "transitioning" view by checking whether a view has parent but is not it's parent child (this **should** be reliable),
    
    Having information on whether the view is in transition or not, we can prevent multiple removals of the same view in every call site (currently only in `removeViewAt` if `parent.removeClippingSubviews == true`).
    
    Another option would be to do just as this PR does: having in mind this "transitioning" state we can pass a flag to `removeViewWithSubviewClippingEnabled` and prevent duplicated removal from parent if we already know that this has been requested.
    
    I can also add override of this method:
    
    ```java
      /*package*/ void removeViewWithSubviewClippingEnabled(View view) {
        this.removeViewWithSubviewClippingEnabled(view, false);
      }
    ```
    
    to make this parameter optional.
    
    [ANDROID] [FIXED] - Handle removal of in-transition views.
    
    Pull Request resolved: #47634
    
    Test Plan: WIP WIP
    
    Reviewed By: javache
    
    Differential Revision: D66539065
    
    Pulled By: tdn120
    
    fbshipit-source-id: cf1add67000ebd1b5dfdb2048461a55deac10b16
    kkafar authored and cipolleschi committed Dec 16, 2024
    Configuration menu
    Copy the full SHA
    81aaf46 View commit details
    Browse the repository at this point in the history
  5. Configuration menu
    Copy the full SHA
    125b0f4 View commit details
    Browse the repository at this point in the history
  6. Configuration menu
    Copy the full SHA
    d4941c7 View commit details
    Browse the repository at this point in the history
  7. Configuration menu
    Copy the full SHA
    c4c52fb View commit details
    Browse the repository at this point in the history
  8. Configuration menu
    Copy the full SHA
    6b22412 View commit details
    Browse the repository at this point in the history
  9. Gradle to 8.11.1 (#48026)

    Summary:
    Pull Request resolved: #48026
    
    This should mitigate this particular issue we're seeing on Windows:
    - #46210
    
    Changelog:
    [Android] [Changed] - Gradle to 8.11.1
    
    Reviewed By: javache
    
    Differential Revision: D66600321
    
    fbshipit-source-id: d58437485222e189d90bcf4d6b41ca956449ed22
    cortinico authored and cipolleschi committed Dec 16, 2024
    Configuration menu
    Copy the full SHA
    e7c4490 View commit details
    Browse the repository at this point in the history

Commits on Dec 17, 2024

  1. Release 0.77.0-rc.3

    #publish-packages-to-npm&next
    react-native-bot committed Dec 17, 2024
    Configuration menu
    Copy the full SHA
    0840eab View commit details
    Browse the repository at this point in the history
  2. [LOCAL] Bump podfile.lock

    cipolleschi committed Dec 17, 2024
    Configuration menu
    Copy the full SHA
    4370860 View commit details
    Browse the repository at this point in the history

Commits on Dec 23, 2024

  1. Restore subclipping view removal (#48329)

    Summary:
    Pull Request resolved: #48329
    
    With the call to `removeView()` removed from `ReactViewClippingManager` in #47634, we're seeing views erroneously sticking around in the layout.
    
    While `removeViewWithSubviewClippingEnabled()` calls `removeViewsInLayout()`, it does not trigger the corresponding side effects of [removeView()](https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-15.0.0_r9/core/java/android/view/ViewGroup.java#5501):
    ```
    public void removeView(View view) {
      if (removeViewInternal(view)) {
    --> requestLayout();
    --> invalidate(true);
      }
    }
    ```
    To compensate, flip `removeViewsInLayout()` to [`removeViews()`](https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-15.0.0_r9/core/java/android/view/ViewGroup.java#5562), which will ensure layout.
    
    Changelog: [Android][Fixed] Restore layout/invalidate during ReactViewClippingManager.removeViewAt()
    
    Reviewed By: javache
    
    Differential Revision: D67398971
    
    fbshipit-source-id: b100db468cc3be6ddc6edd6c6d078a8a0b59a2c1
    Thomas Nardone authored and robhogan committed Dec 23, 2024
    2 Configuration menu
    Copy the full SHA
    e3970a4 View commit details
    Browse the repository at this point in the history
  2. Release 0.77.0-rc.4

    #publish-packages-to-npm&next
    react-native-bot committed Dec 23, 2024
    Configuration menu
    Copy the full SHA
    b0d9ef8 View commit details
    Browse the repository at this point in the history
Loading