React Native / Expo Location (геолокаци)

Expo Location (геолокаци)

Хэрэглэгчийн байршлыг мэдэх нь delivery апп, цаг агаар апп, газрын зураг — олон төрлийн аппликейшнд зайлшгүй шаардлагатай. expo-location package нь GPS координат авах, хаягт хөрвүүлэх (reverse geocoding), байршлын өөрчлөлтийг дагах зэрэг бүх функцийг агуулна.

expo-location суулгах

bash
npx expo install expo-location

Суулгасны дараа iOS болон Android хоёулан байршлын зөвшөөрөл шаардана. Зөвшөөрлийг requestForegroundPermissionsAsync() функцээр авна — энэ нь апп ажиллаж байх үеийн байршил юм. Дэлгэрэнгүй зөвшөөрлийн талаар 35-р хичээлд сурна.

Одоогийн байршил авах

getCurrentPositionAsync() нь хэрэглэгчийн GPS координатыг нэг удаа авна. Энэ нь latitude (өргөрөг) ба longitude (уртраг) агуулсан объект буцаана:

jsx
import { useState } from "react";
import * as Location from "expo-location";
import {
  View,
  Text,
  TouchableOpacity,
  ActivityIndicator,
  StyleSheet,
} from "react-native";

export default function MyLocation() {
  const [location, setLocation] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const getLocation = async () => {
    setLoading(true);
    setError(null);
    try {
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== "granted") {
        setError("Байршлын зөвшөөрөл олгогдоогүй");
        return;
      }

      const loc = await Location.getCurrentPositionAsync({
        accuracy: Location.Accuracy.High,
      });
      setLocation(loc.coords);
    } catch (err) {
      setError("Байршил авахад алдаа гарлаа");
    } finally {
      setLoading(false);
    }
  };

  return (
    <View style={styles.container}>
      <TouchableOpacity
        style={styles.btn}
        onPress={getLocation}
        disabled={loading}
      >
        {loading ? (
          <ActivityIndicator color="#0b1120" />
        ) : (
          <Text style={styles.btnText}>📍 Байршил авах</Text>
        )}
      </TouchableOpacity>

      {error && <Text style={styles.error}>{error}</Text>}

      {location && (
        <View style={styles.card}>
          <Text style={styles.cardLabel}>Өргөрөг (Latitude)</Text>
          <Text style={styles.cardValue}>{location.latitude.toFixed(6)}</Text>

          <Text style={styles.cardLabel}>Уртраг (Longitude)</Text>
          <Text style={styles.cardValue}>{location.longitude.toFixed(6)}</Text>

          <Text style={styles.cardLabel}>Нарийвчлал (Accuracy)</Text>
          <Text style={styles.cardValue}>
            ±{location.accuracy?.toFixed(0)} м
          </Text>
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#0b1120",
    padding: 24,
    gap: 20,
  },
  btn: {
    backgroundColor: "#22d3ee",
    paddingHorizontal: 28,
    paddingVertical: 14,
    borderRadius: 12,
    minWidth: 180,
    alignItems: "center",
  },
  btnText: {
    color: "#0b1120",
    fontWeight: "700",
    fontSize: 16,
  },
  error: {
    color: "#fb7185",
    fontSize: 14,
    textAlign: "center",
  },
  card: {
    backgroundColor: "#0f172a",
    borderRadius: 14,
    padding: 20,
    width: "100%",
    borderWidth: 1,
    borderColor: "#1e293b",
    gap: 4,
  },
  cardLabel: {
    color: "#475569",
    fontSize: 12,
    marginTop: 10,
  },
  cardValue: {
    color: "#22d3ee",
    fontSize: 16,
    fontWeight: "600",
    fontVariant: ["tabular-nums"],
  },
});

Accuracy.High нь GPS ашиглан хамгийн нарийн байршил авах тохиргоо. Батарей хэмнэх бол Accuracy.Balanced эсвэл Accuracy.Low ашиглаж болно.

Reverse geocoding — координатыг хаягт хөрвүүлэх

GPS координат хэрэглэгчид утгагүй харагддаг. reverseGeocodeAsync() функц нь координатыг уншигдахуйц хаягт хөрвүүлнэ:

jsx
import { useState } from "react";
import * as Location from "expo-location";
import {
  View,
  Text,
  TouchableOpacity,
  ActivityIndicator,
  StyleSheet,
} from "react-native";

export default function AddressLookup() {
  const [address, setAddress] = useState(null);
  const [loading, setLoading] = useState(false);

  const findAddress = async () => {
    setLoading(true);
    try {
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== "granted") return;

      const loc = await Location.getCurrentPositionAsync({
        accuracy: Location.Accuracy.Balanced,
      });

      const places = await Location.reverseGeocodeAsync({
        latitude: loc.coords.latitude,
        longitude: loc.coords.longitude,
      });

      if (places.length > 0) {
        setAddress(places[0]);
      }
    } finally {
      setLoading(false);
    }
  };

  const formatAddress = (addr) => {
    const parts = [
      addr.name,
      addr.street,
      addr.district,
      addr.city,
      addr.country,
    ].filter(Boolean);
    return parts.join(", ");
  };

  return (
    <View style={styles.container}>
      <TouchableOpacity
        style={styles.btn}
        onPress={findAddress}
        disabled={loading}
      >
        {loading ? (
          <ActivityIndicator color="#0b1120" />
        ) : (
          <Text style={styles.btnText}>🗺️ Хаяг олох</Text>
        )}
      </TouchableOpacity>

      {address && (
        <View style={styles.result}>
          <Text style={styles.resultTitle}>Таны байршил</Text>
          <Text style={styles.resultAddress}>{formatAddress(address)}</Text>

          <View style={styles.row}>
            <Detail label="Хот" value={address.city} />
            <Detail label="Улс" value={address.country} />
          </View>
        </View>
      )}
    </View>
  );
}

function Detail({ label, value }) {
  return (
    <View style={styles.detail}>
      <Text style={styles.detailLabel}>{label}</Text>
      <Text style={styles.detailValue}>{value ?? "—"}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#0b1120",
    padding: 24,
    justifyContent: "center",
    gap: 20,
  },
  btn: {
    backgroundColor: "#22d3ee",
    padding: 16,
    borderRadius: 12,
    alignItems: "center",
  },
  btnText: {
    color: "#0b1120",
    fontWeight: "700",
    fontSize: 16,
  },
  result: {
    backgroundColor: "#0f172a",
    borderRadius: 14,
    padding: 20,
    borderWidth: 1,
    borderColor: "#1e293b",
    gap: 8,
  },
  resultTitle: {
    color: "#475569",
    fontSize: 12,
    fontWeight: "500",
  },
  resultAddress: {
    color: "#f1f5f9",
    fontSize: 15,
    lineHeight: 22,
  },
  row: {
    flexDirection: "row",
    gap: 12,
    marginTop: 8,
  },
  detail: {
    flex: 1,
    backgroundColor: "#0b1120",
    borderRadius: 8,
    padding: 10,
  },
  detailLabel: {
    color: "#475569",
    fontSize: 11,
    marginBottom: 2,
  },
  detailValue: {
    color: "#22d3ee",
    fontSize: 14,
    fontWeight: "600",
  },
});

Байршлын өөрчлөлтийг дагах

Хэрэглэгч хөдөлж байх үед байршлыг тасралтгүй шинэчлэх бол watchPositionAsync() ашиглана. Апп хаагдах үед subscription-г цэвэрлэхээ мартуузай:

jsx
import { useState, useEffect, useRef } from "react";
import * as Location from "expo-location";
import { View, Text, StyleSheet } from "react-native";

export default function LiveTracker() {
  const [coords, setCoords] = useState(null);
  const subscriptionRef = useRef(null);

  useEffect(() => {
    let active = true;

    (async () => {
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== "granted" || !active) return;

      subscriptionRef.current = await Location.watchPositionAsync(
        {
          accuracy: Location.Accuracy.High,
          timeInterval: 3000, // 3 секунд тутамд
          distanceInterval: 10, // эсвэл 10 метр зайд
        },
        (loc) => setCoords(loc.coords),
      );
    })();

    return () => {
      active = false;
      subscriptionRef.current?.remove();
    };
  }, []);

  return (
    <View style={styles.container}>
      <Text style={styles.label}>Шууд байршил</Text>
      {coords ? (
        <>
          <Text style={styles.coord}>
            {coords.latitude.toFixed(5)}, {coords.longitude.toFixed(5)}
          </Text>
          <Text style={styles.speed}>
            Хурд:{" "}
            {coords.speed != null
              ? (coords.speed * 3.6).toFixed(1) + " км/ц"
              : "—"}
          </Text>
        </>
      ) : (
        <Text style={styles.waiting}>GPS сигнал хайж байна...</Text>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#0b1120",
    gap: 12,
  },
  label: {
    color: "#475569",
    fontSize: 13,
    fontWeight: "500",
  },
  coord: {
    color: "#22d3ee",
    fontSize: 20,
    fontWeight: "700",
    fontVariant: ["tabular-nums"],
  },
  speed: {
    color: "#94a3b8",
    fontSize: 14,
  },
  waiting: {
    color: "#475569",
    fontSize: 14,
  },
});

coords.speed нь метр/секундаар ирдэг тул × 3.6 хийж км/цагт хөрвүүлнэ. Байршлыг Монголд ашиглах жишээ: delivery апп дагаж буй жолооч, hiking trail tracker, нэг газраас нөгөөд хугацаа тооцоолох.

Дараагийн хичээлд:

Push Notification — хэрэглэгч апп нээлгүй байхад мэдэгдэл явуулах үндсэн функц сурна.