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
@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
inHostComponents
, 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.