react-native-skia: Memory Leak on Android

Description

Ever since we released Skia to prod a few days ago we’ve had a few reports from our Android user’s that the app has gotten really slow and unresponsive at time’s which make me believe there is some sort of memory leak.

Version

0.1.200

Steps to reproduce

Create a BoxShadow component (e.g. example component below that will add a BoxShadow to its children). Render multiple versions of these in a large flatList and notice the performance of the app decreases

Snack, code example, screenshot, or link to a repository

import { Box, BoxShadow, Canvas, rect, rrect } from '@shopify/react-native-skia'
import React, { FC, useState } from 'react'
import { LayoutChangeEvent, Platform, View } from 'react-native'
import { useAppThemeSelector } from '~/context/ThemeContext'
import { hexWithOpacity } from '~/utils/colors'
import { SPACING } from '~/utils/spacing'
import { COLORS_V2, Themes } from '~/utils/theme'

type ShadowType = 'card' | 'chatBubble' | 'modal' | 'footer'

interface SkiaShadowProps {
	canvasRadius: number
	type: ShadowType
}

/**
 * Performant shadow using Skia
 * Please note that the shadow will create a padding around the child component
 * The default padding is SPACING.s8
 * @param canvasPadding - padding around the child component
 * @param canvasRadius - radius of the child component
 * @param children - child component
 * @param type - type of shadow
 * @returns
 */
const SkiaShadow: FC<SkiaShadowProps> = ({ canvasRadius, children, type }) => {
	const theme = useAppThemeSelector(state => state.theme)
	const [layout, setLayout] = useState({ width: 0, height: 0 })

	const onLayout = (event: LayoutChangeEvent) => {
		const { width, height } = event.nativeEvent.layout
		setLayout({ width, height })
	}

	const cardWidth = layout.width
	const cardHeight = layout.height

	let canvasPadding = { top: SPACING.s8, left: SPACING.s8, right: SPACING.s8, bottom: SPACING.s8 }

	if (type === 'chatBubble') {
		canvasPadding = { top: SPACING.s4, left: SPACING.s8, right: SPACING.s8, bottom: SPACING.s12 }
	}

	if (type === 'modal') {
		canvasPadding = { top: SPACING.s16, left: SPACING.s16, right: SPACING.s16, bottom: SPACING.s16 }
	}
	return (
		<View
			style={{
				position: 'relative',
			}}>
			<View style={{ position: 'absolute' }}>
				<Canvas
					style={{
						width: cardWidth + canvasPadding.left + canvasPadding.right,
						height: cardHeight + canvasPadding.top + canvasPadding.bottom,
					}}>
					<Box
						box={rrect(
							rect(
								canvasPadding.left,
								canvasPadding.top,
								cardWidth - (canvasPadding.left + canvasPadding.right),
								cardHeight - (canvasPadding.top + canvasPadding.bottom),
							),
							canvasRadius,
							canvasRadius,
						)}
						color="transparent">
						{type === 'card' && (
							<>
								{theme === Themes.light ? (
									<BoxShadow dx={0} dy={2} blur={6} spread={2} color={COLORS_V2[theme].cardShadow} />
								) : (
									<>
										<BoxShadow
											dx={0}
											dy={0}
											blur={1}
											spread={0}
											color={hexWithOpacity(COLORS_V2[theme].cardShadow2, 0.5)}
										/>
										<BoxShadow
											dx={0}
											dy={4}
											blur={6}
											spread={0}
											color={hexWithOpacity(COLORS_V2[theme].cardShadow2, 0.36)}
										/>
										<BoxShadow
											dx={0}
											dy={0}
											blur={0}
											spread={1}
											color={hexWithOpacity(COLORS_V2[theme].cardShadowInner, 0.04)}
											inner
										/>
									</>
								)}
							</>
						)}
						{type === 'chatBubble' && (
							<>
								{theme === Themes.light ? (
									<>
										<BoxShadow
											dx={6}
											dy={8}
											blur={12}
											spread={-4}
											color={hexWithOpacity(COLORS_V2[theme].chatBubbleShadow, 0.15)}
										/>
										<BoxShadow
											dx={0}
											dy={0}
											blur={1}
											spread={0}
											color={hexWithOpacity(COLORS_V2[theme].chatBubbleShadow, 0.31)}
										/>
									</>
								) : (
									<>
										<BoxShadow
											dx={0}
											dy={0}
											blur={1}
											spread={0}
											color={hexWithOpacity(COLORS_V2[theme].cardShadow2, 0.5)}
										/>
										<BoxShadow
											dx={0}
											dy={8}
											blur={12}
											spread={-4}
											color={hexWithOpacity(COLORS_V2[theme].cardShadow2, 0.36)}
										/>
										<BoxShadow
											dx={0}
											dy={0}
											blur={0}
											spread={1}
											color={hexWithOpacity(COLORS_V2[theme].cardShadowInner, 0.04)}
											inner
										/>
									</>
								)}
							</>
						)}
						{type === 'footer' && (
							<>
								<BoxShadow
									dx={0}
									dy={4}
									blur={8}
									spread={0}
									color={hexWithOpacity(COLORS_V2[theme].chatBubbleShadow, 0.15)}
								/>
							</>
						)}
						{type === 'modal' && (
							<>
								{theme === Themes.light ? (
									<BoxShadow dx={0} dy={-2} blur={32} spread={-8} color={COLORS_V2[theme].cardShadow} />
								) : (
									<BoxShadow
										dx={0}
										dy={16}
										blur={32}
										spread={-12}
										color={hexWithOpacity(COLORS_V2[theme].cardShadow, 0.2)}
									/>
								)}
							</>
						)}
					</Box>
				</Canvas>
			</View>
			<View
				onLayout={onLayout}
				style={{
					borderRadius: canvasRadius,
					paddingTop: canvasPadding.top,
					paddingLeft: canvasPadding.left,
					paddingRight: canvasPadding.right,
					paddingBottom: canvasPadding.bottom,
				}}>
				{children}
			</View>
		</View>
	)
}

export default SkiaShadow

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 1
  • Comments: 19

Most upvoted comments

@bj97301 are you on latest version? It was supposed to be fixed but I havnt updated yet either to test

@LeviWilliams I’m closing this for now, we’ve done some deprecations that will help with this (to keep the supported feature set nice and clean). But let’s keep coordinating if something comes up.

@MatiPl01 We have not been able to fully resolve these issues within our application. We have been able to delay the issue be memoizing some of our skia components, though overtime we still see it occur.

@wcandillon Could you confirm what device you were attempting to reproduce on as well? We will see if we can dream up something to showcase this easier for you. The issue demonstrated in the video is what compounds in numerous screens/components in our app which eventually leads to the crash, though I understand it seems evasive.

@LeviWilliams: Thank you for spending the time to investigate this. We will definitely look at this. if you set shouldUseJSDomOnNative to true in HostComponents, do you have the same issue? I’d something I would be eager to try.

@LeviWilliams We would love to have a look at an example if you have one. In the meantime, I will try to reproduce the issue from @walterholohan if possible.