Skip to content
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

[Reanimated] The view with tag N gets view-flattened on Android. To disable view-flattening, set collapsable={false} on this component. #5006

Closed
devoren opened this issue Aug 29, 2023 · 5 comments
Labels
Missing info The user didn't precise the problem enough Missing repro This issue need minimum repro scenario Needs review Issue is ready to be reviewed by a maintainer Platform: Android This issue is specific to Android Platform: iOS This issue is specific to iOS

Comments

@devoren
Copy link

devoren commented Aug 29, 2023

Description

I have a component that uses the measure function from reanimated via useAnimatedRef. This works very well. My components (inputs) are inside a scrollview and when I navigate back or to another screen I get this warn:
[Reanimated] The view with tag 21089 gets view-flattened on Android. To disable view-flattening, set collapsable={false} on this component.

This warning appears when the keyboard is visible. I saw this solution #3188 (comment) but it didn't help. Or am I missing something? Do all my components inside the ScrollView need to be animated? I set collapsable false for ScrollView

Component:

import React, { Component, FC } from 'react';
import { ScrollViewProps, StyleSheet, useWindowDimensions } from 'react-native';
import Reanimated, {
	AnimatedRef,
	MeasuredDimensions,
	interpolate,
	measure,
	scrollTo,
	useAnimatedRef,
	useAnimatedScrollHandler,
	useAnimatedStyle,
	useSharedValue,
	useWorkletCallback,
} from 'react-native-reanimated';
import { useSmoothKeyboardHandler } from './useSmoothKeyboardHandler';

const BOTTOM_OFFSET = 50;

const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
	children,
	keyboardShouldPersistTaps = 'handled',
	...rest
}) => {
	const scrollViewAnimatedRef = useAnimatedRef<Reanimated.ScrollView>();
	const scrollPosition = useSharedValue(0);
	const position = useSharedValue(0);
	const layout = useSharedValue<MeasuredDimensions | null>(null);
	const fakeViewHeight = useSharedValue(0);
	const keyboardHeight = useSharedValue(0);
	const tag = useSharedValue(-1);
	const initialKeyboardSize = useSharedValue(0);
	const scrollBeforeKeyboardMovement = useSharedValue(0);

	const { height } = useWindowDimensions();

	const onScroll = useAnimatedScrollHandler(
		{
			onScroll: (e) => {
				position.value = e.contentOffset.y;
			},
		},
		[],
	);
	const measureByTag = useWorkletCallback((viewTag: number) => {
		return measure((() => viewTag) as unknown as AnimatedRef<Component<{}, {}, any>>);
	}, []);

	/**
	 * Function that will scroll a ScrollView as keyboard gets moving
	 */
	const maybeScroll = useWorkletCallback((e: number, animated: boolean = false) => {
		fakeViewHeight.value = e;

		const visibleRect = height - keyboardHeight.value;
		const point = (layout.value?.pageY || 0) + (layout.value?.height || 0);

		if (visibleRect - point <= BOTTOM_OFFSET) {
			const interpolatedScrollTo = interpolate(
				e,
				[initialKeyboardSize.value, keyboardHeight.value],
				[0, keyboardHeight.value - (height - point) + BOTTOM_OFFSET],
			);
			const targetScrollY = Math.max(interpolatedScrollTo, 0) + scrollPosition.value;
			scrollTo(scrollViewAnimatedRef, 0, targetScrollY, animated);
		}
	}, []);

	useSmoothKeyboardHandler(
		{
			onStart: (e) => {
				'worklet';

				const keyboardWillChangeSize = keyboardHeight.value !== e.height && e.height > 0;
				const keyboardWillAppear = e.height > 0 && keyboardHeight.value === 0;
				const keyboardWillHide = e.height === 0;
				if (keyboardWillChangeSize) {
					initialKeyboardSize.value = keyboardHeight.value;
				}

				if (keyboardWillHide) {
					// on back transition need to interpolate as [0, keyboardHeight]
					initialKeyboardSize.value = 0;
					scrollPosition.value = scrollBeforeKeyboardMovement.value;
				}

				if (keyboardWillAppear || keyboardWillChangeSize) {
					// persist scroll value
					scrollPosition.value = position.value;
					// just persist height - later will be used in interpolation
					keyboardHeight.value = e.height;
				}

				// focus was changed
				if (tag.value !== e.target || keyboardWillChangeSize) {
					tag.value = e.target;

					if (tag.value !== -1) {
						// save position of focused text input when keyboard starts to move
						layout.value = measureByTag(e.target);
						// save current scroll position - when keyboard will hide we'll reuse
						// this value to achieve smooth hide effect
						scrollBeforeKeyboardMovement.value = position.value;
					}
				}
			},
			onMove: (e) => {
				'worklet';

				maybeScroll(e.height);
			},
			onEnd: (e) => {
				'worklet';

				keyboardHeight.value = e.height;
				scrollPosition.value = position.value;

				if (e.target !== -1 && e.height !== 0) {
					const prevLayout = layout.value;
					// just be sure, that view is no overlapped (i.e. focus changed)
					layout.value = measureByTag(e.target);
					maybeScroll(e.height, true);
					// do layout substitution back to assure there will be correct
					// back transition when keyboard hides
					layout.value = prevLayout;
				}
			},
		},
		[height],
	);

	const view = useAnimatedStyle(
		() => ({
			height: fakeViewHeight.value,
		}),
		[],
	);

	return (
		<Reanimated.ScrollView
			ref={scrollViewAnimatedRef}
			style={styles.screenContainer as any}
			keyboardShouldPersistTaps={keyboardShouldPersistTaps}
			onScroll={onScroll}
			scrollEventThrottle={16}
			collapsable={false}
			{...rest}>
			{children}
			<Reanimated.View style={view} collapsable={false} />
		</Reanimated.ScrollView>
	);
};

export default KeyboardAwareScrollView;

export const styles = StyleSheet.create({
	screenContainer: {
		flex: 1,
	},
});

Steps to reproduce

  1. Use measure function in component
  2. Open keyboard
  3. Navigate to another screen using navigation

Snack or a link to a repository

NA

Reanimated version

^3.4.2

React Native version

0.72.4

Platforms

Android, iOS

JavaScript runtime

Hermes

Workflow

Expo managed workflow

Architecture

Paper (Old Architecture)

Build type

Debug mode

Device

Android emulator

Device model

Pixel 4 API 31

Acknowledgements

Yes

@devoren devoren added the Needs review Issue is ready to be reviewed by a maintainer label Aug 29, 2023
@github-actions github-actions bot added Missing info The user didn't precise the problem enough Missing repro This issue need minimum repro scenario labels Aug 29, 2023
@github-actions
Copy link

Hey! 👋

It looks like you've omitted a few important sections from the issue template.

Please complete Snack or a link to a repository section.

@github-actions
Copy link

Hey! 👋

The issue doesn't seem to contain a minimal reproduction.

Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?

@github-actions github-actions bot added Platform: Android This issue is specific to Android Platform: iOS This issue is specific to iOS labels Aug 29, 2023
@idrakimuhamad
Copy link

You're using the example from the react-native-keyboard-controller? I'm getting it too from this component.

Also, after using this component, my app crash on Android on release build, whenever user focus on TextInput and then pressing back on the header. I'm suspecting that the something to do with the keyboard being dismiss un-naturally by going back (maybe).

@devoren
Copy link
Author

devoren commented Sep 27, 2023

@idrakimuhamad yes

@badalsaibo
Copy link

Adding a simple check if the animatedRef is valid while using its values seems to remove the warning for me.

const myRef = animatedRef();

const handleSomething = () => {
  if (myRef && myRef.current) {
      // use your myRef safely. now
  }
}

@devoren devoren closed this as completed Nov 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Missing info The user didn't precise the problem enough Missing repro This issue need minimum repro scenario Needs review Issue is ready to be reviewed by a maintainer Platform: Android This issue is specific to Android Platform: iOS This issue is specific to iOS
Projects
None yet
Development

No branches or pull requests

3 participants