rnmapbox / maps

A Mapbox react native module for creating custom maps
MIT License
2.28k stars 847 forks source link

[Bug]: Black screen when device is offline #3683

Open MahmoudMousaHamad opened 3 weeks ago

MahmoudMousaHamad commented 3 weeks ago

Mapbox Implementation

Mapbox

Mapbox Version

11.7.0

React Native Version

0.73.2

Platform

iOS, Android

@rnmapbox/maps version

10.1.33

Standalone component to reproduce

import React, { useState, useRef } from 'react';
import { View, TouchableOpacity, Platform, Dimensions, Alert } from 'react-native';
import Mapbox from '@rnmapbox/maps';
import geoViewport from '@mapbox/geo-viewport';

const MAPBOX_VECTOR_TILE_SIZE = 512;

const Home = () => {
  const mapRef = useRef(null);
  const cameraRef = useRef(null);
  const [followUserLocation, setFollowUserLocation] = useState(true);
  const [userLocation, setUserLocation] = useState([-80.1918, 25.7617]);
  const [showBasemapSelector, setShowBasemapSelector] = useState(false);
  const [rotation, setRotation] = useState(0);
  const [centerToDisplay, setCenterToDisplay] = useState([-80.1918, 25.7617]);

  const startDownload = async () => {
    const {width, height} = Dimensions.get('window');

    const region = {
      name: "Region",
      styleURL: "selectedStyle.url",
      bounds: geoViewport.bounds(
        centerToDisplay,
        12,
        [width, height],
        MAPBOX_VECTOR_TILE_SIZE,
      ),
      minZoom: 1,
      maxZoom: 16,
    };

    try {
      const pack = await Mapbox.offlineManager.createPack(
        {
          name: region.name,
          styleURL: region.styleURL,
          bounds: [
            [region.bounds[0], region.bounds[1]],
            [region.bounds[2], region.bounds[3]],
          ],
          minZoom: region.minZoom,
          maxZoom: region.maxZoom,
          metadata: {},
        },
      );
    } catch (error) {
      console.error('Error downloading region:', error);
      Alert.alert(
        'Download Failed',
        'Please try selecting a smaller area or check your internet connection.',
      );
    } finally {
    }
  };

  const handleCameraChanged = (event) => {
    setRotation(event.properties.heading);
    setCenterToDisplay(event.properties.center);
  };

  const resetNorth = () => {
    cameraRef.current?.setCamera({
      heading: 0,
      animationDuration: 500,
    });
  };

  const zoomIn = () => {
    cameraRef.current?.zoomTo(cameraRef.current?.getZoom() + 1, 500);
  };

  const zoomOut = () => {
    cameraRef.current?.zoomTo(cameraRef.current?.getZoom() - 1, 500);
  };

  return (
    <View style={styles.page}>
      <Mapbox.MapView
        onPress={() => setShowBasemapSelector(false)}
        attributionEnabled={false}
        ref={mapRef}
        style={styles.map}
        styleJSON={JSON.stringify({ /* style json object */})}
        logoEnabled={false}
        scaleBarEnabled={true}
        scaleBarPosition={{
          bottom: 45,
        }}
        onTouchMove={() => followUserLocation && setFollowUserLocation(false)}
        onCameraChanged={handleCameraChanged}>
        <Mapbox.UserLocation
          visible={true}
          animated={true}
          minDisplacement={10}
          showsUserHeadingIndicator={true}
          requestsAlwaysUse={true}
          onUpdate={loc => {
            if (followUserLocation) {
              setUserLocation([loc?.coords?.longitude, loc?.coords.latitude]);
            }
          }}
        />
        <Mapbox.Camera
          maxZoomLevel={19}
          minZoomLevel={0}
          ref={cameraRef}
          centerCoordinate={userLocation || [-80.1918, 25.7617]}
          defaultSettings={{
            centerCoordinate: userLocation,
            zoomLevel: 8,
            animationDuration: 1,
          }}
          allowUpdates={true}
          animationMode="flyTo"
        />
      </Mapbox.MapView>

      {/* Compass */}
      <TouchableOpacity
        activeOpacity={0.8}
        style={styles.compass}
        onPress={resetNorth}>
      </TouchableOpacity>

      {/* Zoom controls */}
      <View style={styles.zoomControls}>
        <TouchableOpacity style={styles.zoomButton} onPress={zoomIn}>
        </TouchableOpacity>
        <TouchableOpacity style={styles.zoomButton} onPress={zoomOut}>
        </TouchableOpacity>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  page: {
    flex: 1,
  },
  map: {
    flex: 1,
  },
  compass: {
    position: 'absolute',
    top: 20,
    right: 20,
    backgroundColor: 'white',
    borderRadius: 20,
    padding: 8,
    elevation: 5,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
  },
  zoomControls: {
    position: 'absolute',
    right: 20,
    bottom: 100,
    backgroundColor: 'white',
    borderRadius: 10,
    elevation: 5,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
  },
  zoomButton: {
    padding: 10,
  },
});

export default Home;

Observed behavior and steps to reproduce

The issue is that I get a black screen when offline although there are map styles that were downloaded in the region. I followed the example in the documentation: https://rnmapbox.github.io/docs/examples/Map/OfflineExample.

image

Note that the issue happens if the user exists and kills the app and then opens it again or when cache is cleared. Clearing cache consistently leads to this issue of the black screen appearing if device is offline.

Expected behavior

The expected behavior is for mapbox to use the downloaded map styles when the user is offline.

Notes / preliminary analysis

No response

Additional links and references

I think that the issue that I'm facing is similar to the following issue from the MB Flutter repo: https://github.com/mapbox/mapbox-maps-flutter/issues/661