maplibre-react-native
maplibre-react-native copied to clipboard
Crash on Android - TextureViewRend
Describe and reproduce the Bug
Summary
I haven't pinned down exactly what, but on Android when rendering Heatmaps and Markers, zooming out causes this error.
--------- beginning of crash
08-07 18:36:04.663 417 514 F libc : Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x2827a96c79fe71 in tid 514 (TextureViewRend), pid 417 (om.six33.first2)
Here's some code for the event markers we use
Single Marker
import { MarkerView, PointAnnotation } from '@maplibre/maplibre-react-native';
import { Pressable, View, StyleSheet } from 'react-native';
// Animation imports removed
import CustomText from '../../../UI/CustomText';
import { FontAwesome6 as Icon } from '@expo/vector-icons';
import SocialEvent from '../../../../redux/models/Event/Event';
import { useMap } from '../../../Map/MapContext';
import { useTranslation } from 'react-i18next';
import { memo, useEffect, useMemo } from 'react';
import { colors } from '../../../../utils/colors';
import { MAP_FRIEND_COLOR } from '../../../../utils/constants';
import { dayjsWithCoords } from '../../../../utils/dayjsUtils';
import { getColorPalette } from '../../../../utils/getColorPalette';
import CrewLogo from '../../../UI/Crews/CrewLogo';
import NameText from '../../../UI/NameText';
import { Image } from 'expo-image';
const underlayImg = require('../../../../assets/images/event-underlay.png');
const THRESHOLD_ICON_ZOOM = 8;
const THRESHOLD_DETAILS_ZOOM = 12;
interface EventMarkerProps {
event: SocialEvent;
onPressZoomIn?: () => void;
onPressEvent?: (eventId: string) => void;
}
export const EventMarker = memo(
({ event, onPressEvent, onPressZoomIn }: EventMarkerProps) => {
const { mapZoom } = useMap();
const { t } = useTranslation();
const isCrewEvent = useMemo(() => Boolean(event.crew?.id), [event]);
const colorPalette = useMemo(() => {
const primaryColor =
event.crew?.primaryColor ||
(event.private ? MAP_FRIEND_COLOR : colors.accent);
const secondaryColor =
event.crew?.secondaryColor ||
(event.private ? MAP_FRIEND_COLOR : colors.accent);
return getColorPalette(primaryColor, secondaryColor);
}, [event.crew?.primaryColor, event.crew?.secondaryColor, event.private]);
const colorOnDark = useMemo(() => {
return colorPalette.isPrimaryLight ? colorPalette.primary : '#ffffff';
}, [colorPalette]);
const markerStyle = useMemo(
() => [
styles.markerContainer,
event.crew ? styles.largeMarker : styles.smallMarker,
{ backgroundColor: `${colorPalette.colorOnDark}99` },
],
[event.crew, colorPalette.colorOnDark],
);
const dotStyle = useMemo(
() => [styles.dot, { backgroundColor: colorPalette.secondary }],
[colorPalette.secondary],
);
if (!event.location?.coordinates) {
return null;
}
return (
<PointAnnotation
id={event.id.toString()}
coordinate={[
event.location.coordinates[0], // longitude
event.location.coordinates[1], // latitude
]}
>
<Pressable
onPress={() => {
if (mapZoom < THRESHOLD_DETAILS_ZOOM) {
onPressZoomIn?.();
return;
}
onPressEvent?.(event.id);
}}
className="w-56 h-56 flex items-center justify-center"
pointerEvents="box-none"
>
<View className="rounded-full overflow-hidden" style={[markerStyle]}>
{event.crew?.logo && (
<CrewLogo className="w-5 h-5" logoUrl={event.crew.logo} />
)}
{!event.crew && <View style={dotStyle} />}
</View>
{mapZoom >= THRESHOLD_DETAILS_ZOOM && (
<View className="absolute top-0 left-0 w-56 h-56 flex items-center justify-center -z-[1]">
<Image
source={underlayImg}
className="absolute -top-10 -left-10 -right-10 -bottom-10"
contentFit="contain"
style={{
transform: [{ rotate: '-90deg' }],
}}
/>
<View
className="flex-1 justify-end flex-col"
pointerEvents="none"
>
<NameText
isVerified={event.creator.verified}
type={isCrewEvent ? 'crew' : 'user'}
name={event.crew?.name || event.creator.username}
color={colorOnDark}
textStyle={{
fontSize: 10,
}}
/>
</View>
<View
className="w-full h-16 flex-row items-center justify-center"
pointerEvents="none"
>
<View className="flex-1 justify-end items-center flex-row space-x-1 opacity-80">
<CustomText className="text-xs text-right text-white" bold>
{dayjsWithCoords(
event.date,
event.location.coordinates[1],
event.location.coordinates[0],
).format('D MMM\nHH:mm')}
</CustomText>
</View>
<View className="w-20 h-full" />
<View className="flex-1 flex-col items-start">
<View className="flex flex-row items-center space-x-1 opacity-80">
<Icon
name={event.private ? 'people-group' : 'earth-americas'}
size={10}
color="white"
/>
<CustomText className="text-xs text-white" bold>
{event.private
? t('events.event.private')
: t('events.event.public')}
</CustomText>
</View>
<View className="flex flex-row items-center space-x-1 opacity-80">
<Icon name="user" solid size={9} color="white" />
<CustomText className="text-xs text-white" bold>
{event.attendeesCount || event.attending?.length || 0}
</CustomText>
</View>
</View>
</View>
<View
className="flex-1 w-full flex items-center"
pointerEvents="box-none"
>
<View className="w-full" pointerEvents="none">
<CustomText
numberOfLines={2}
uppercase
className="text-center text-white w-full text-lg"
bold
style={{ color: colorOnDark }}
>
{event.name}
</CustomText>
</View>
{onPressEvent && (
<View className="p-2">
<CustomText
bold
uppercase
className="text-xs text-darkBlue-400"
>
{t('events.event.viewDetails')}
</CustomText>
</View>
)}
</View>
</View>
)}
</Pressable>
</PointAnnotation>
);
},
);
const styles = StyleSheet.create({
markerContainer: {
alignItems: 'center',
justifyContent: 'center',
borderRadius: 40,
},
smallMarker: {
width: 10,
height: 10,
},
largeMarker: {
width: 24,
height: 24,
},
dot: {
width: 4,
height: 4,
borderRadius: 4,
},
});
Rendering the multiple markers and heatmap when zoomed out
import {
CircleLayer,
HeatmapLayer,
ShapeSource,
} from '@maplibre/maplibre-react-native';
import React, { memo, useEffect, useMemo, useRef } from 'react';
import { useGetNearbyEventsQuery } from '../../../../redux/api/events';
import { useGetNearbyEventsGuestQuery } from '../../../../redux/api/events-guest';
import { useTypedSelector } from '../../../../redux/store';
import { colors } from '../../../../utils/colors';
import { useMap } from '../../../Map/MapContext';
import { isPointInBounds } from '../../../../utils/mapUtils';
import dayjs from 'dayjs';
import Color from 'color';
import { EventMarker } from './EventMarker';
import { useGridSearchCoords } from '../../useGridSearchCoords';
interface EventMarkersProps {
onPressEvent?: (eventId: string, type: 'zoom' | 'event') => void;
}
const HEATMAP_ZOOM_THRESHOLD = 9;
const EventMarkers = memo(({ onPressEvent }: EventMarkersProps) => {
const { mapRegion, mapZoom } = useMap();
const guestMode = useTypedSelector((state) => state.config.guestMode);
const gridCoords = useGridSearchCoords();
// Use appropriate API based on guest mode
const { data: nearbyEvents } = guestMode
? useGetNearbyEventsGuestQuery(
{
latitude: gridCoords.latitude,
longitude: gridCoords.longitude,
radius: gridCoords.radius,
},
{
skip: !gridCoords.latitude || !gridCoords.longitude,
},
)
: useGetNearbyEventsQuery(
{
latitude: gridCoords.latitude,
longitude: gridCoords.longitude,
radius: gridCoords.radius,
},
{
skip: !gridCoords.latitude || !gridCoords.longitude,
},
);
const validEvents = useMemo(
() =>
[...(nearbyEvents || [])]
.filter((event) => dayjs(event.date).isBefore(dayjs().add(1, 'month')))
.filter(
(event) =>
event.location?.coordinates?.[0] &&
event.location?.coordinates?.[1],
),
[nearbyEvents, mapRegion],
);
const validEventsForDisplay = useMemo(
() =>
validEvents
.filter((event) => {
if (!mapRegion || !mapRegion.properties.visibleBounds) return true;
return isPointInBounds(
{
latitude: event.location.coordinates[1],
longitude: event.location.coordinates[0],
},
mapRegion.properties.visibleBounds,
);
})
.sort((a, b) =>
a.participants
? a.participants.length - (b.participants?.length || 0)
: 0,
)
.slice(0, 7),
[validEvents, mapRegion],
);
// Create GeoJSON feature collection for heatmap
const heatmapData = useMemo(() => {
return {
type: 'FeatureCollection' as const,
features: (validEvents || []).map((event) => ({
type: 'Feature' as const,
properties: {
weight: 0.1,
},
geometry: {
type: 'Point' as const,
coordinates: [
event.location.coordinates[0], // longitude
event.location.coordinates[1], // latitude
],
},
})),
};
}, [validEvents]);
if (!validEvents.length) {
return null;
}
return (
<>
<ShapeSource
key="event-heatmap-source"
id="event-heatmap-source"
shape={heatmapData}
>
<HeatmapLayer
id="event-heatmap"
style={{
heatmapIntensity: [
'interpolate',
['linear'],
['zoom'],
HEATMAP_ZOOM_THRESHOLD - 1,
1,
HEATMAP_ZOOM_THRESHOLD,
0,
],
heatmapColor: [
'interpolate',
['linear'],
['heatmap-density'],
0,
Color(colors.accent).alpha(0).rgb().string(),
1,
Color(colors.accent).alpha(1).rgb().string(),
],
heatmapRadius: ['interpolate', ['linear'], ['zoom'], 5, 2, 14, 4],
heatmapOpacity: [
'interpolate',
['linear'],
['zoom'],
HEATMAP_ZOOM_THRESHOLD - 1,
0.7,
HEATMAP_ZOOM_THRESHOLD,
0,
],
}}
/>
</ShapeSource>
{mapZoom > HEATMAP_ZOOM_THRESHOLD &&
validEventsForDisplay.map((event) => {
return (
<EventMarker
onPressEvent={() => {
onPressEvent?.(event.id, 'event');
}}
onPressZoomIn={() => {
onPressEvent?.(event.id, 'zoom');
}}
key={event.id}
event={event}
/>
);
})}
</>
);
});
export default memo(EventMarkers);
@maplibre/maplibre-react-native Version
10.2.0
Which platforms does this occur on?
Android Device
Which frameworks does this occur on?
Expo
Which architectures does this occur on?
Old Architecture
Environment
expo-env-info 1.3.4 environment info:
System:
OS: macOS 15.5
Shell: 5.9 - /bin/zsh
Binaries:
Node: 22.9.0 - /usr/local/bin/node
Yarn: 1.22.18 - /opt/homebrew/bin/yarn
npm: 11.5.1 - /opt/homebrew/bin/npm
Watchman: 2024.12.02.00 - /opt/homebrew/bin/watchman
Managers:
CocoaPods: 1.15.2 - /Users/kirilkrsteski/.rbenv/shims/pod
SDKs:
iOS SDK:
Platforms: DriverKit 24.2, iOS 18.2, macOS 15.2, tvOS 18.2, visionOS 2.2, watchOS 11.2
IDEs:
Android Studio: 2024.2 AI-242.23339.11.2421.12550806
Xcode: 16.2/16C5032a - /usr/bin/xcodebuild
npmPackages:
expo: ~52.0.47 => 52.0.47
react: 18.3.1 => 18.3.1
react-native: 0.76.9 => 0.76.9
npmGlobalPackages:
eas-cli: 16.17.3
expo-cli: 6.3.12
Expo Workflow: bare
Thanks for the report. Please find a minimal reproduction we can simply copy/paste as a single file to reproduce.
Thanks for the report. Please find a minimal reproduction we can simply copy/paste as a single file to reproduce.
@KiwiKilian I'm trying to pinpoint the exact issue myself, I thought it was some of the markers but I'm not sure that's the case anymore. I noticed using a mapStyle (specifically those from CARTO) would lock the map/app to 60fps (instead of 120) and tank performance.
Will comment here if I find anything more useful, unless this stuff with the styles is anything you already know
Can you try if this also occurs with surfaceView set to true on the MapView? Surface view shall be the default in the following releases. It has long be the default from MLN, but this library was never properly updated to reflect this.