React Native Maps not showing Markers on Android, even though API data is fetched correctly

I'm building a React Native app to display location markers on a map using react-native-maps. I'm using the Google Maps provider on Android. My problem is that the map loads, but the markers are not visible, even though I can confirm that my API call is successful and returns a valid array of location data.

MapsScreen.jsx:-

import React, { useState, useEffect, useRef, useMemo } from "react";
import {
  View,
  Text,
  StyleSheet,
  ActivityIndicator,
  Alert,
  SafeAreaView,
  TouchableOpacity,
  StatusBar,
  Modal,
} from "react-native";
import MapView, { Marker, Callout, PROVIDER_GOOGLE } from "react-native-maps";
import { useRoute, useNavigation } from "@react-navigation/native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { fetchMaps } from "../services/maps";

const StatusIndicator = ({ text }) => (
  <SafeAreaView style={styles.statusContainer}>
    <StatusBar barStyle="light-content" backgroundColor="#181d23" />
    <ActivityIndicator size="large" color="#27F0C9" />
    <Text style={styles.statusText}>{text}</Text>
  </SafeAreaView>
);

const isValidCoord = (lat, lng) =>
  Number.isFinite(lat) &&
  Number.isFinite(lng) &&
  Math.abs(lat) <= 90 &&
  Math.abs(lng) <= 180;

const MapsAnalysis = () => {
  const [points, setPoints] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);
  const [showInfo, setShowInfo] = useState(false);
  const [mapReady, setMapReady] = useState(false);

  const route = useRoute();
  const navigation = useNavigation();
  const { userId } = route.params;
  const mapRef = useRef(null);

  useEffect(() => {
    const getLocations = async () => {
      if (!userId) {
        setError("User ID not found. Cannot fetch map data.");
        setIsLoading(false);
        return;
      }
      try {
        setIsLoading(true);
        setError(null);

        const data = await fetchMaps(userId);
        const formattedData = Array.isArray(data)
          ? data
              .map((point) => {
                const lat = Number(point.latitude);
                const lng = Number(point.longitude);
                return {
                  ...point,
                  latitude: Number.isFinite(lat) ? lat : NaN,
                  longitude: Number.isFinite(lng) ? lng : NaN,
                  
                  pleasantness:
                    point.pleasantness != null ? Number(point.pleasantness) : null,
                  eventfulness:
                    point.eventfulness != null ? Number(point.eventfulness) : null,
                  annoyance:
                    point.annoyance != null ? Number(point.annoyance) : null,
                };
              })
              .filter((p) => isValidCoord(p.latitude, p.longitude))
          : [];

        console.log("Points to plot:", formattedData.length, formattedData.slice(0, 3));
        setPoints(formattedData);
      } catch (err) {
        console.error("Failed to fetch map data:", err);
        setError(err.message || "Could not load map data.");
        Alert.alert("Error", err.message || "Could not load map data.");
      } finally {
        setIsLoading(false);
      }
    };
    getLocations();
  }, [userId]);

  const coords = useMemo(
    () => points.map((p) => ({ latitude: p.latitude, longitude: p.longitude })),
    [points]
  );

  useEffect(() => {
    if (!mapReady || !mapRef.current || coords.length === 0) return;

    if (coords.length === 1) {
      
      const p = coords[0];
      mapRef.current.animateToRegion(
        {
          latitude: p.latitude,
          longitude: p.longitude,
          latitudeDelta: 0.02,
          longitudeDelta: 0.02,
        },
        600
      );
    } else {
      
      mapRef.current.fitToCoordinates(coords, {
        edgePadding: { top: 100, right: 100, bottom: 100, left: 100 },
        animated: true,
      });
    }
  }, [mapReady, coords]);

  if (isLoading) {
    return <StatusIndicator text="Loading Map Data..." />;
  }

  if (error) {
    return <StatusIndicator text={error} />;
  }

  if (points.length === 0) {
    return <StatusIndicator text="No map points found for this user." />;
  }

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar barStyle="light-content" backgroundColor="#181d23" />

      <View style={styles.header}>
        <TouchableOpacity onPress={() => navigation.goBack()} style={{ padding: 5 }}>
          <Icon name="chevron-left" size={28} color="#fff" />
        </TouchableOpacity>
        <Text style={styles.headerTitle}>Analysis Map</Text>
        <TouchableOpacity onPress={() => setShowInfo(true)} style={{ padding: 5 }}>
          <Icon name="help-circle-outline" size={24} color="#fff" />
        </TouchableOpacity>
      </View>

      <Modal transparent visible={showInfo} animationType="fade">
        <View style={styles.overlay}>
          <View style={styles.infoModalBox}>
            <Text style={styles.infoTitle}>About The Map</Text>
            <Text style={styles.infoText}>
              This map shows the locations of all saved sound analyses. Tap on any pin to see the details of that specific analysis.
            </Text>
            <TouchableOpacity style={styles.closeBtn} onPress={() => setShowInfo(false)}>
              <Text style={styles.closeText}>Got It</Text>
            </TouchableOpacity>
          </View>
        </View>
      </Modal>

      <MapView
        ref={mapRef}
        provider={PROVIDER_GOOGLE}
        style={styles.map}
        // Delhi
        initialRegion={{
          latitude: 28.6139,
          longitude: 77.209,
          latitudeDelta: 0.5,
          longitudeDelta: 0.5,
        }}
        onMapReady={() => setMapReady(true)}
        onLayout={() => setMapReady(true)}
      >
        {points.map((point, index) => (
          <Marker
            key={`${point.id ?? index}`}
            coordinate={{ latitude: point.latitude, longitude: point.longitude }}
            
          >
            <Callout>
              <View style={styles.calloutView}>
                <Text style={styles.calloutTitle}>Analysis Details</Text>
                <Text>
                  Pleasantness:{" "}
                  {typeof point.pleasantness === "number"
                    ? point.pleasantness.toFixed(2)
                    : "N/A"}
                </Text>
                <Text>
                  Eventfulness:{" "}
                  {typeof point.eventfulness === "number"
                    ? point.eventfulness.toFixed(2)
                    : "N/A"}
                </Text>
                <Text>
                  Annoyance:{" "}
                  {typeof point.annoyance === "number"
                    ? point.annoyance.toFixed(2)
                    : "N/A"}
                </Text>
              </View>
            </Callout>
          </Marker>
        ))}
      </MapView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#181d23",
  },
  map: {
    flex: 1,
  },
  header: {
    width: "100%",
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
    paddingVertical: 14,
    paddingHorizontal: 10,
    paddingTop: 30,
    backgroundColor: "#181d23",
  },
  headerTitle: {
    fontSize: 20,
    color: "#fff",
    fontWeight: "bold",
  },
  statusContainer: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#181d23",
  },
  statusText: {
    marginTop: 20,
    fontSize: 16,
    color: "#B6BAC5",
  },
  calloutView: {
    padding: 10,
    width: 200,
  },
  calloutTitle: {
    fontWeight: "bold",
    fontSize: 16,
    marginBottom: 5,
  },
  overlay: {
    flex: 1,
    backgroundColor: "rgba(0,0,0,0.7)",
    justifyContent: "center",
    alignItems: "center",
  },
  infoModalBox: {
    backgroundColor: "#2D2E32",
    padding: 25,
    borderRadius: 15,
    width: "85%",
    borderWidth: 1,
    borderColor: "rgba(255, 255, 255, 0.1)",
  },
  infoTitle: {
    color: "#FFFFFF",
    fontSize: 18,
    fontWeight: "bold",
    textAlign: "center",
    marginBottom: 15,
  },
  infoText: {
    color: "#E0E0E0",
    fontSize: 15,
    lineHeight: 22,
    marginBottom: 25,
  },
  closeBtn: {
    backgroundColor: "#27F0C9",
    paddingVertical: 12,
    paddingHorizontal: 30,
    alignSelf: "center",
    borderRadius: 8,
  },
  closeText: {
    color: "#181d23",
    fontWeight: "bold",
    fontSize: 16,
  },
});

export default MapsAnalysis;

Here is the debugger of react native

Maps On Emulator Maps On Emulator Maps On Emulator

DRF Terminal (Getting Latitude and Longitude from DB successfully)

Root Cause

The primary issue is incompatibility with React Native's New Architecture (also called Bridgeless Mode). When newArchEnabled is set to true, the react-native-maps library has rendering problems specifically on Android, where markers either don't appear at all or only appear after re-renders or screen navigation changes.​

Immediate Solution

Disable the New Architecture temporarily by modifying your app.json file :

{
  "expo": {
    "android": {
      "newArchEnabled": false
    }
  }
}

After this change, rebuild your app using:

  • expo prebuild (if using Expo), or

  • Create a new development build

This resolves the marker rendering issue without downgrading React Native or expo versions.

Found the issue!

In your code (line 147-148), you have both onMapReady and onLayout setting the same state:

onMapReady={() => setMapReady(true)}
onLayout={() => setMapReady(true)}  // ← Remove this line

onLayout fires before the map is actually ready on Android, causing markers to render too early and become invisible.​

Fix: Remove the onLayout callback completely:

<MapView
  ref={mapRef}
  provider={PROVIDER_GOOGLE}
  style={styles.map}
  initialRegion={{
    latitude: 28.6139,
    longitude: 77.209,
    latitudeDelta: 0.5,
    longitudeDelta: 0.5,
  }}
  onMapReady={() => setMapReady(true)}  // Keep only this
>

Also, you're already conditionally rendering markers correctly on line 174:

{points.map((point, index) => (
  <Marker ... />
))}

But since mapReady is used in the camera animation useEffect (line 100), not for marker rendering, your markers are always rendering regardless of map readiness.

Better approach - wrap your markers:

{mapReady && points.map((point, index) => (
  <Marker
    key={`${point.id ?? index}`}
    coordinate={{ latitude: point.latitude, longitude: point.longitude }}
  >
    <Callout>...</Callout>
  </Marker>
))}

This ensures markers only render after the map is fully ready.

Use react-native-maps 1.20.1 at least or older to get oldArch support. That will fix your issue.

Вернуться на верх