Cross-platform mapy w React Native: Android, iOS oraz Web

Cały kod źródłowy oraz przykładowy projekt dostępny jest tutaj: https://github.com/AndreiMaksimovich/react-native-maps-web–demo

AndroidiOSWeb

Web demo: https://demos.amaxsoftware.com/01.react-native-maps-web/

Problem

Mapy są jedną z najczęściej spotykanych funkcji we współczesnych aplikacjach. W React Native domyślnym wyborem do obsługi map jest biblioteka react-native-maps. Choć działa dobrze na iOS i Androidzie, nie zapewnia wsparcia dla wersji web. W rzeczywistości żadna z popularnych, ugruntowanych bibliotek mapowych nie oferuje obecnie pełnego wsparcia dla kompilacji webowej.

Istnieje biblioteka teovillanueva/react-native-web-maps, jednak od dłuższego czasu nie jest aktywnie rozwijana i wciąż zapewnia jedynie bardzo ograniczoną funkcjonalność.

Cel

Naszym celem było stworzenie prostego rozszerzenia/wrapper-a dla powszechnie używanej, de facto standardowej biblioteki react-native-maps. Rozszerzenie powinno być możliwie najmniej inwazyjne, a jednocześnie zapewniać kluczowe funkcje działające bezproblemowo na Androidzie, iOS oraz Web. W szczególności powinno:

  • Wyświetlać mapy w spójny sposób na Androidzie, iOS i Web
  • Udostępniać API do podstawowych interakcji z mapą, takich jak zmiana położenia kamery
  • Obsługiwać wyświetlanie znaczników (markers) na mapie
  • Obsługiwać polilinie dla tras
  • Pokazywać lokalizację użytkownika wraz z kierunkiem/headingiem urządzenia

Rozwiązanie

Mapy

Najprostszym i bezpośrednim sposobem na uzyskanie wsparcia map wieloplatformowych jest rozszerzenie istniejącej biblioteki o własny wrapper. Wrapper ten pośredniczy (proxy) w wywołaniach oryginalnej funkcjonalności na Androidzie i iOS, a jednocześnie dostarcza własne komponenty odtwarzające to samo zachowanie w wersji Web, z identycznymi lub rozszerzonymi właściwościami.

Biblioteki bazowe

  • Android i iOS – używamy react-native-maps.
  • Web – korzystamy z vis.gl/react-google-maps.

Struktura projektu

  • /src/react-native-maps-web/index.ts – eksportuje komponenty w tym samym stylu co react-native-maps. Dzięki temu zależności mogą być łatwo ręcznie podmieniane, refaktoryzowane lub aliasowane w bundlerze webowym.
  • Każdy proxowany komponent (np. Component.tsx) ma swój odpowiednik webowy (Component.web.tsx), który jest automatycznie używany podczas budowania wersji web.
  • /src/react-native-web/Types.ts – definiuje rozszerzone typy i właściwości. Opcje specyficzne dla Web są poprzedzane prefiksem web. Na przykład MapView zawiera webGoogleMapsApiKey, wymagany wyłącznie w implementacji web (podczas gdy iOS korzysta z prebuild hardcoded configuration, a Android z pliku manifest).

Kontrola mapy
Komponent MapView udostępnia mapRef, co pozwala na programową kontrolę mapy. Podstawowy interfejs zdefiniowany jest w Types.ts (Map), a implementacje specyficzne dla platform znajdują się w MapRN.ts oraz MapWeb.ts.

Lokalizacja użytkownika

Lokalizacja użytkownika obsługiwana jest poprzez własny provider znajdujący się w /src/foreground-location-provider/.

  • Android i iOS – oparte na bibliotece expo-location.
  • Web – wykorzystuje wbudowane API navigator.geolocation.

Choć expo-location technicznie działa w przeglądarce, jednak okazało się, że działa czasami błędne i niestabilne, dlatego zdecydowaliśmy się na natywną implementację przeglądarkową.

Kierunek urządzenia (Device Heading)

Dane o kierunku urządzenia pochodzą z innego customowego providera w /src/device-heading-provider/.

  • Android i iOS – zaimplementowane przy pomocy react-native-compass-heading.
  • Web – korzysta z API AbsoluteOrientationSensor.