6.7 KiB
6.7 KiB
React Native Customer Tracking Integration Guide
This guide outlines how to connect and implement live driver location tracking in your React Native Customer App.
📦 Dependencies
Install the required packages in your React Native project:
npm install socket.io-client react-native-maps
🔌 1. Tracking Socket Service (trackingSocket.ts)
Create a socket helper to handle the namespace connection (/tracking) and events:
import { io, Socket } from "socket.io-client";
const SOCKET_URL = "http://<YOUR_BACKEND_IP>:8000/tracking";
export interface DriverLocationUpdate {
orderId: string;
latitude: number;
longitude: number;
heading?: number;
speedKph?: number;
recordedAt: string;
}
export interface TrackingStatusUpdate {
orderId: string;
status: "OUT_FOR_DELIVERY" | "DELIVERED";
timestamp: string;
}
class TrackingSocketService {
private socket: Socket | null = null;
connect(token: string): Socket {
if (this.socket?.connected) {
return this.socket;
}
this.socket = io(SOCKET_URL, {
auth: { token },
transports: ["websocket"],
reconnection: true,
});
this.socket.on("connect", () => {
console.log("Connected to Tracking WebSocket Namespace");
});
this.socket.on("connect_error", (error) => {
console.error("Socket Connection Error:", error);
});
return this.socket;
}
joinOrderTracking(orderId: string) {
this.socket?.emit("join_order_tracking", { orderId });
}
leaveOrderTracking(orderId: string) {
this.socket?.emit("leave_order_tracking", { orderId });
}
onDriverLocationUpdate(callback: (data: DriverLocationUpdate) => void) {
this.socket?.on("driver_location_update", callback);
}
onOrderStatusUpdate(callback: (data: TrackingStatusUpdate) => void) {
this.socket?.on("order_tracking_status", callback);
}
disconnect(orderId: string) {
if (this.socket) {
this.leaveOrderTracking(orderId);
this.socket.off("driver_location_update");
this.socket.off("order_tracking_status");
this.socket.disconnect();
this.socket = null;
}
}
}
export const trackingSocketService = new TrackingSocketService();
🪝 2. Customer Tracking Hook (useOrderTracking.ts)
Manage room state subscription, events listening, and automatic teardown on component unmount:
import { useEffect, useState } from "react";
import { trackingSocketService, DriverLocationUpdate } from "./trackingSocket";
export function useOrderTracking(orderId: string, userJwtToken: string) {
const [driverLocation, setDriverLocation] =
useState<DriverLocationUpdate | null>(null);
const [orderStatus, setOrderStatus] = useState<string | null>(null);
useEffect(() => {
if (!orderId || !userJwtToken) return;
const socket = trackingSocketService.connect(userJwtToken);
trackingSocketService.joinOrderTracking(orderId);
trackingSocketService.onDriverLocationUpdate((data) => {
if (data.orderId === orderId) {
setDriverLocation(data);
}
});
trackingSocketService.onOrderStatusUpdate((data) => {
if (data.orderId === orderId) {
setOrderStatus(data.status);
}
});
return () => {
trackingSocketService.disconnect(orderId);
};
}, [orderId, userJwtToken]);
return { driverLocation, orderStatus };
}
🗺️ 3. Tracking Screen Component
Render Google Maps with the destination address, live driver position, and auto-camera alignment:
import React, { useRef, useEffect } from "react";
import { StyleSheet, View, Text } from "react-native";
import MapView, { Marker, PROVIDER_GOOGLE } from "react-native-maps";
import { useOrderTracking } from "./useOrderTracking";
interface TrackingScreenProps {
orderId: string;
token: string;
customerDropoff: { latitude: number; longitude: number };
}
export default function OrderTrackingScreen({
orderId,
token,
customerDropoff,
}: TrackingScreenProps) {
const mapRef = useRef<MapView>(null);
const { driverLocation, orderStatus } = useOrderTracking(orderId, token);
useEffect(() => {
if (driverLocation && mapRef.current) {
mapRef.current.animateToRegion(
{
latitude: driverLocation.latitude,
longitude: driverLocation.longitude,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
},
1000,
);
}
}, [driverLocation]);
return (
<View style={styles.container}>
<MapView
ref={mapRef}
provider={PROVIDER_GOOGLE}
style={styles.map}
initialRegion={{
latitude: customerDropoff.latitude,
longitude: customerDropoff.longitude,
latitudeDelta: 0.02,
longitudeDelta: 0.02,
}}
>
{/* Customer dropoff point */}
<Marker
coordinate={customerDropoff}
title="Delivery Location"
pinColor="red"
/>
{/* Live Driver */}
{driverLocation && (
<Marker
coordinate={{
latitude: driverLocation.latitude,
longitude: driverLocation.longitude,
}}
title="Your Delivery Driver"
anchor={{ x: 0.5, y: 0.5 }}
rotation={driverLocation.heading ?? 0}
>
<View style={styles.driverMarker}>
<Text style={styles.driverEmoji}>🛵</Text>
</View>
</Marker>
)}
</MapView>
<View style={styles.statusContainer}>
<Text style={styles.statusTitle}>
Status:{" "}
{orderStatus === "OUT_FOR_DELIVERY"
? "Out for Delivery 🛵"
: orderStatus || "Preparing"}
</Text>
{driverLocation?.speedKph !== undefined && (
<Text style={styles.statusSubtitle}>
Driver speed: {Math.round(driverLocation.speedKph)} km/h
</Text>
)}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
justifyContent: "flex-end",
alignItems: "center",
},
map: {
...StyleSheet.absoluteFillObject,
},
driverMarker: {
backgroundColor: "#FFF",
padding: 6,
borderRadius: 20,
borderWidth: 2,
borderColor: "#FF7F00",
elevation: 4,
},
driverEmoji: {
fontSize: 20,
},
statusContainer: {
position: "absolute",
bottom: 40,
backgroundColor: "white",
padding: 16,
borderRadius: 12,
width: "90%",
elevation: 5,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
},
statusTitle: {
fontSize: 16,
fontWeight: "bold",
color: "#333",
},
statusSubtitle: {
fontSize: 14,
color: "#666",
marginTop: 4,
},
});