Prerequisites
- React Native development environment setup
 - Node.js and npm/yarn installed
 - Access to InControl API credentials (Client ID and Client Secret)
 - Basic knowledge of GraphQL and React Native
 
Tutorial Overview
We'll create a driver app that enables:
- Remote start/stop of charging sessions
 - Real-time session monitoring
 - Push notifications for session completion
 - Charging station discovery
 
Step 1: Project Setup
Initialize React Native Project
npx react-native init InControlDriverApp
cd InControlDriverApp
Install Required Dependencies
npm install @apollo/client graphql react-native-push-notification
npm install @react-native-async-storage/async-storage
npm install react-native-permissions
npm install @react-native-community/netinfo
npm install react-native-maps
npm install @react-native-community/geolocation
For iOS, also run:
cd ios && pod install && cd ..
Step 2: API Configuration
Set up Apollo Client
Create src/apollo/client.js:
import { ApolloClient, InMemoryCache, createHttpLink, from } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import AsyncStorage from '@react-native-async-storage/async-storage';
const httpLink = createHttpLink({
  uri: 'https://your-instance.inchargeus.net/api/graphql', // Replace with your instance
});
const authLink = setContext(async (_, { headers }) => {
  const token = await AsyncStorage.getItem('apiToken');
  
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
      'Content-Type': 'application/json',
    }
  }
});
export const client = new ApolloClient({
  link: from([authLink, httpLink]),
  cache: new InMemoryCache(),
  defaultOptions: {
    watchQuery: {
      errorPolicy: 'all'
    }
  }
});
Authentication Service
Create src/services/authService.js:
import AsyncStorage from '@react-native-async-storage/async-storage';
export class AuthService {
  static async authenticateWithCredentials(clientId, clientSecret) {
    try {
      const credentials = `${clientId}:${clientSecret}`;
      const encodedCredentials = btoa(credentials); // Base64 encoding
      
      await AsyncStorage.setItem('apiToken', encodedCredentials);
      return true;
    } catch (error) {
      console.error('Authentication failed:', error);
      return false;
    }
  }
  static async getToken() {
    return await AsyncStorage.getItem('apiToken');
  }
  static async logout() {
    await AsyncStorage.removeItem('apiToken');
  }
}
Step 3: Location Service
Set up Location Tracking
Create src/services/locationService.js:
import Geolocation from '@react-native-community/geolocation';
import { PermissionsAndroid, Platform, Alert } from 'react-native';
export class LocationService {
  static async requestLocationPermission() {
    if (Platform.OS === 'android') {
      try {
        const granted = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
          {
            title: 'Location Permission',
            message: 'This app needs access to your location to find nearby charging stations.',
            buttonNeutral: 'Ask Me Later',
            buttonNegative: 'Cancel',
            buttonPositive: 'OK',
          }
        );
        return granted === PermissionsAndroid.RESULTS.GRANTED;
      } catch (err) {
        console.warn(err);
        return false;
      }
    }
    return true; // iOS permissions are handled by react-native-maps
  }
  static getCurrentPosition() {
    return new Promise((resolve, reject) => {
      Geolocation.getCurrentPosition(
        (position) => {
          resolve({
            latitude: position.coords.latitude,
            longitude: position.coords.longitude,
          });
        },
        (error) => {
          console.error('Location error:', error);
          reject(error);
        },
        {
          enableHighAccuracy: true,
          timeout: 15000,
          maximumAge: 10000,
        }
      );
    });
  }
  static watchPosition(callback) {
    return Geolocation.watchPosition(
      (position) => {
        callback({
          latitude: position.coords.latitude,
          longitude: position.coords.longitude,
        });
      },
      (error) => {
        console.error('Location watch error:', error);
      },
      {
        enableHighAccuracy: true,
        distanceFilter: 10, // Update every 10 meters
      }
    );
  }
  static clearWatch(watchId) {
    Geolocation.clearWatch(watchId);
  }
  static calculateDistance(lat1, lon1, lat2, lon2) {
    const R = 6371; // Radius of the Earth in kilometers
    const dLat = (lat2 - lat1) * Math.PI / 180;
    const dLon = (lon2 - lon1) * Math.PI / 180;
    const a = 
      Math.sin(dLat/2) * Math.sin(dLat/2) +
      Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * 
      Math.sin(dLon/2) * Math.sin(dLon/2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    const distance = R * c; // Distance in kilometers
    return distance;
  }
}
Step 4: GraphQL Queries and Mutations
Define GraphQL Operations
Create src/graphql/operations.js:
import { gql } from '@apollo/client';
// Query to get available charging stations
export const GET_CHARGING_STATIONS = gql`
  query GetChargingStations($filter: ChargingStationFilter) {
    chargingStations(filter: $filter) {
      id
      name
      location {
        latitude
        longitude
        address
      }
      connectors {
        id
        type
        status
        power
      }
      availability
      status
    }
  }
`;
// Query to get active sessions for a driver
export const GET_ACTIVE_SESSIONS = gql`
  query GetActiveSessions($driverId: ID!) {
    sessions(filter: { driverId: $driverId, status: ACTIVE }) {
      id
      status
      startTime
      estimatedEndTime
      energyDelivered
      chargingStation {
        id
        name
        location {
          address
        }
      }
      connector {
        id
        type
        power
      }
    }
  }
`;
// Mutation to start a charging session
export const START_CHARGING_SESSION = gql`
  mutation StartChargingSession($input: StartSessionInput!) {
    startChargingSession(input: $input) {
      success
      session {
        id
        status
        startTime
        chargingStation {
          name
        }
      }
      errors {
        message
        code
      }
    }
  }
`;
// Mutation to stop a charging session
export const STOP_CHARGING_SESSION = gql`
  mutation StopChargingSession($sessionId: ID!) {
    stopChargingSession(sessionId: $sessionId) {
      success
      session {
        id
        status
        endTime
        totalEnergyDelivered
        totalCost
      }
      errors {
        message
        code
      }
    }
  }
`;
// Subscription for real-time session updates
export const SESSION_UPDATES = gql`
  subscription SessionUpdates($sessionId: ID!) {
    sessionUpdated(sessionId: $sessionId) {
      id
      status
      energyDelivered
      estimatedTimeRemaining
      currentPower
    }
  }
`;
Step 4: React Native Components
Main App Component
Create src/App.js:
import React, { useEffect, useState } from 'react';
import { ApolloProvider } from '@apollo/client';
import { client } from './apollo/client';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { AuthService } from './services/authService';
import LoginScreen from './screens/LoginScreen';
import DashboardScreen from './screens/DashboardScreen';
import ChargingStationsScreen from './screens/ChargingStationsScreen';
import StationMapScreen from './screens/StationMapScreen';
import { setupPushNotifications } from './services/notificationService';
const Stack = createStackNavigator();
export default function App() {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    checkAuthStatus();
    setupPushNotifications();
  }, []);
  const checkAuthStatus = async () => {
    const token = await AuthService.getToken();
    setIsAuthenticated(!!token);
    setLoading(false);
  };
  if (loading) {
    return null; // Add loading spinner here
  }
  return (
    <ApolloProvider client={client}>
      <NavigationContainer>
        <Stack.Navigator>
          {!isAuthenticated ? (
            <Stack.Screen 
              name="Login" 
              component={LoginScreen}
              options={{ title: 'InControl Driver' }}
            />
          ) : (
            <>
              <Stack.Screen 
                name="Dashboard" 
                component={DashboardScreen}
                options={{ title: 'Dashboard' }}
              />
              <Stack.Screen 
                name="StationMap" 
                component={StationMapScreen}
                options={{ title: 'Charging Stations Map' }}
              />
              <Stack.Screen 
                name="Stations" 
                component={ChargingStationsScreen}
                options={{ title: 'Charging Stations' }}
              />
            </>
          )}
        </Stack.Navigator>
      </NavigationContainer>
    </ApolloProvider>
  );
}
Login Screen
Create src/screens/LoginScreen.js:
import React, { useState } from 'react';
import {
  View,
  Text,
  TextInput,
  TouchableOpacity,
  StyleSheet,
  Alert,
} from 'react-native';
import { AuthService } from '../services/authService';
export default function LoginScreen({ navigation }) {
  const [clientId, setClientId] = useState('');
  const [clientSecret, setClientSecret] = useState('');
  const [loading, setLoading] = useState(false);
  const handleLogin = async () => {
    if (!clientId || !clientSecret) {
      Alert.alert('Error', 'Please enter both Client ID and Client Secret');
      return;
    }
    setLoading(true);
    const success = await AuthService.authenticateWithCredentials(clientId, clientSecret);
    
    if (success) {
      navigation.replace('Dashboard');
    } else {
      Alert.alert('Error', 'Authentication failed. Please check your credentials.');
    }
    setLoading(false);
  };
  return (
    <View style={styles.container}>
      <Text style={styles.title}>InControl Driver App</Text>
      
      <TextInput
        style={styles.input}
        placeholder="Client ID"
        value={clientId}
        onChangeText={setClientId}
        autoCapitalize="none"
      />
      
      <TextInput
        style={styles.input}
        placeholder="Client Secret"
        value={clientSecret}
        onChangeText={setClientSecret}
        secureTextEntry
        autoCapitalize="none"
      />
      
      <TouchableOpacity 
        style={styles.button} 
        onPress={handleLogin}
        disabled={loading}
      >
        <Text style={styles.buttonText}>
          {loading ? 'Logging in...' : 'Login'}
        </Text>
      </TouchableOpacity>
    </View>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    textAlign: 'center',
    marginBottom: 40,
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 15,
    marginVertical: 10,
    borderRadius: 8,
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 15,
    borderRadius: 8,
    marginTop: 20,
  },
  buttonText: {
    color: 'white',
    textAlign: 'center',
    fontWeight: 'bold',
  },
});
Enhanced Dashboard Screen
Create src/screens/DashboardScreen.js:
import React from 'react';
import {
  View,
  Text,
  TouchableOpacity,
  StyleSheet,
  ScrollView,
} from 'react-native';
import { useQuery } from '@apollo/client';
import { GET_ACTIVE_SESSIONS } from '../graphql/operations';
import ActiveSessionCard from '../components/ActiveSessionCard';
export default function DashboardScreen({ navigation }) {
  const driverId = 'current-driver-id'; // Get from user context
  
  const { data, loading, error, refetch } = useQuery(GET_ACTIVE_SESSIONS, {
    variables: { driverId },
    pollInterval: 30000, // Poll every 30 seconds
  });
  return (
    <ScrollView style={styles.container}>
      <Text style={styles.title}>Dashboard</Text>
      
      <View style={styles.buttonRow}>
        <TouchableOpacity 
          style={[styles.actionButton, styles.mapButton]}
          onPress={() => navigation.navigate('StationMap')}
        >
          <Text style={styles.buttonText}>πΊοΈ Station Map</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={[styles.actionButton, styles.listButton]}
          onPress={() => navigation.navigate('Stations')}
        >
          <Text style={styles.buttonText}>π Station List</Text>
        </TouchableOpacity>
      </View>
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>Active Sessions</Text>
        {loading ? (
          <Text>Loading sessions...</Text>
        ) : error ? (
          <Text>Error loading sessions</Text>
        ) : data?.sessions?.length > 0 ? (
          data.sessions.map(session => (
            <ActiveSessionCard 
              key={session.id} 
              session={session}
              onRefresh={refetch}
            />
          ))
        ) : (
          <Text style={styles.noSessions}>No active charging sessions</Text>
        )}
      </View>
    </ScrollView>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  buttonRow: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 20,
  },
  actionButton: {
    flex: 1,
    padding: 15,
    borderRadius: 8,
    marginHorizontal: 5,
  },
  mapButton: {
    backgroundColor: '#34C759',
  },
  listButton: {
    backgroundColor: '#007AFF',
  },
  buttonText: {
    color: 'white',
    textAlign: 'center',
    fontWeight: 'bold',
  },
  section: {
    marginBottom: 20,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  noSessions: {
    textAlign: 'center',
    color: '#666',
    fontStyle: 'italic',
  },
});
Active Session Card Component
Create src/components/ActiveSessionCard.js:
import React, { useEffect } from 'react';
import {
  View,
  Text,
  TouchableOpacity,
  StyleSheet,
  Alert,
} from 'react-native';
import { useMutation, useSubscription } from '@apollo/client';
import { STOP_CHARGING_SESSION, SESSION_UPDATES } from '../graphql/operations';
import { showNotification } from '../services/notificationService';
export default function ActiveSessionCard({ session, onRefresh }) {
  const [stopSession, { loading: stopping }] = useMutation(STOP_CHARGING_SESSION);
  
  // Subscribe to real-time updates
  const { data: updateData } = useSubscription(SESSION_UPDATES, {
    variables: { sessionId: session.id }
  });
  useEffect(() => {
    if (updateData?.sessionUpdated?.status === 'COMPLETED') {
      showNotification({
        title: 'Charging Complete',
        message: `Your charging session at ${session.chargingStation.name} has completed.`,
      });
      onRefresh();
    }
  }, [updateData]);
  const handleStopSession = () => {
    Alert.alert(
      'Stop Charging',
      'Are you sure you want to stop this charging session?',
      [
        { text: 'Cancel', style: 'cancel' },
        { 
          text: 'Stop', 
          style: 'destructive',
          onPress: async () => {
            try {
              const result = await stopSession({
                variables: { sessionId: session.id }
              });
              
              if (result.data?.stopChargingSession?.success) {
                Alert.alert('Success', 'Charging session stopped successfully');
                onRefresh();
              } else {
                Alert.alert('Error', 'Failed to stop charging session');
              }
            } catch (error) {
              Alert.alert('Error', 'Network error occurred');
            }
          }
        }
      ]
    );
  };
  const currentSession = updateData?.sessionUpdated || session;
  return (
    <View style={styles.card}>
      <Text style={styles.stationName}>
        {session.chargingStation.name}
      </Text>
      <Text style={styles.address}>
        {session.chargingStation.location.address}
      </Text>
      
      <View style={styles.details}>
        <Text>Status: {currentSession.status}</Text>
        <Text>Energy: {currentSession.energyDelivered} kWh</Text>
        {currentSession.currentPower && (
          <Text>Power: {currentSession.currentPower} kW</Text>
        )}
        {currentSession.estimatedTimeRemaining && (
          <Text>Time Remaining: {currentSession.estimatedTimeRemaining} min</Text>
        )}
      </View>
      <TouchableOpacity 
        style={styles.stopButton}
        onPress={handleStopSession}
        disabled={stopping}
      >
        <Text style={styles.stopButtonText}>
          {stopping ? 'Stopping...' : 'Stop Charging'}
        </Text>
      </TouchableOpacity>
    </View>
  );
}
const styles = StyleSheet.create({
  card: {
    backgroundColor: 'white',
    padding: 15,
    borderRadius: 8,
    marginBottom: 10,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  stationName: {
    fontSize: 16,
    fontWeight: 'bold',
  },
  address: {
    color: '#666',
    marginBottom: 10,
  },
  details: {
    marginBottom: 15,
  },
  stopButton: {
    backgroundColor: '#FF3B30',
    padding: 12,
    borderRadius: 6,
  },
  stopButtonText: {
    color: 'white',
    textAlign: 'center',
    fontWeight: 'bold',
  },
});
Dedicated Station Map Screen
Create src/screens/StationMapScreen.js:
import React, { useState, useEffect } from 'react';
import {
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
  Alert,
  Dimensions,
  ScrollView,
} from 'react-native';
import MapView, { Marker, PROVIDER_GOOGLE, Circle } from 'react-native-maps';
import { useQuery, useMutation } from '@apollo/client';
import { GET_CHARGING_STATIONS, START_CHARGING_SESSION } from '../graphql/operations';
import { LocationService } from '../services/locationService';
const { width, height } = Dimensions.get('window');
export default function StationMapScreen({ navigation }) {
  const [userLocation, setUserLocation] = useState(null);
  const [selectedStation, setSelectedStation] = useState(null);
  const [searchRadius, setSearchRadius] = useState(5000); // 5km default
  const [mapRegion, setMapRegion] = useState({
    latitude: 37.78825,
    longitude: -122.4324,
    latitudeDelta: 0.0922,
    longitudeDelta: 0.0421,
  });
  const { data, loading, error, refetch } = useQuery(GET_CHARGING_STATIONS, {
    variables: {
      filter: {
        availability: 'AVAILABLE',
        ...(userLocation && {
          location: {
            latitude: userLocation.latitude,
            longitude: userLocation.longitude,
            radius: searchRadius,
          }
        })
      }
    }
  });
  const [startSession, { loading: starting }] = useMutation(START_CHARGING_SESSION);
  useEffect(() => {
    initializeLocation();
  }, []);
  useEffect(() => {
    // Watch for location changes
    let watchId;
    if (userLocation) {
      watchId = LocationService.watchPosition((newLocation) => {
        setUserLocation(newLocation);
        refetch();
      });
    }
    
    return () => {
      if (watchId) {
        LocationService.clearWatch(watchId);
      }
    };
  }, [userLocation, refetch]);
  const initializeLocation = async () => {
    const hasPermission = await LocationService.requestLocationPermission();
    
    if (hasPermission) {
      try {
        const location = await LocationService.getCurrentPosition();
        setUserLocation(location);
        setMapRegion({
          ...location,
          latitudeDelta: 0.02,
          longitudeDelta: 0.02,
        });
        refetch();
      } catch (error) {
        Alert.alert('Location Error', 'Could not get your current location');
      }
    } else {
      Alert.alert('Permission Denied', 'Location permission is required to find nearby stations');
    }
  };
  const handleStartCharging = async (stationId, connectorId) => {
    try {
      const result = await startSession({
        variables: {
          input: {
            chargingStationId: stationId,
            connectorId: connectorId,
            driverId: 'current-driver-id',
          }
        }
      });
      if (result.data?.startChargingSession?.success) {
        Alert.alert('Success', 'Charging session started successfully', [
          { 
            text: 'OK', 
            onPress: () => {
              setSelectedStation(null);
              navigation.navigate('Dashboard');
            }
          }
        ]);
      } else {
        const error = result.data?.startChargingSession?.errors?.[0]?.message;
        Alert.alert('Error', error || 'Failed to start charging session');
      }
    } catch (error) {
      Alert.alert('Error', 'Network error occurred');
    }
  };
  const getMarkerColor = (station) => {
    const availableConnectors = station.connectors.filter(c => c.status === 'AVAILABLE').length;
    if (availableConnectors === 0) return '#FF3B30';
    if (availableConnectors <= 2) return '#FF9500';
    return '#34C759';
  };
  const getDistanceText = (station) => {
    if (!userLocation) return '';
    const distance = LocationService.calculateDistance(
      userLocation.latitude,
      userLocation.longitude,
      station.location.latitude,
      station.location.longitude
    );
    return distance < 1 ? `${Math.round(distance * 1000)}m` : `${distance.toFixed(1)}km`;
  };
  const adjustSearchRadius = (newRadius) => {
    setSearchRadius(newRadius);
    // Update map region to show the new search area
    if (userLocation) {
      const latitudeDelta = (newRadius / 111320) * 2.5; // Rough conversion
      const longitudeDelta = latitudeDelta;
      setMapRegion({
        ...userLocation,
        latitudeDelta,
        longitudeDelta,
      });
    }
    refetch();
  };
  if (loading && !data) {
    return (
      <View style={styles.centered}>
        <Text>Loading charging stations...</Text>
      </View>
    );
  }
  if (error) {
    return (
      <View style={styles.centered}>
        <Text>Error loading stations</Text>
        <TouchableOpacity style={styles.retryButton} onPress={() => refetch()}>
          <Text style={styles.retryText}>Retry</Text>
        </TouchableOpacity>
      </View>
    );
  }
  return (
    <View style={styles.container}>
      {/* Search Radius Controls */}
      <View style={styles.radiusControls}>
        <ScrollView horizontal showsHorizontalScrollIndicator={false}>
          {[1000, 2000, 5000, 10000, 20000].map(radius => (
            <TouchableOpacity
              key={radius}
              style={[
                styles.radiusButton,
                searchRadius === radius && styles.activeRadiusButton
              ]}
              onPress={() => adjustSearchRadius(radius)}
            >
              <Text style={[
                styles.radiusButtonText,
                searchRadius === radius && styles.activeRadiusButtonText
              ]}>
                {radius < 1000 ? `${radius}m` : `${radius/1000}km`}
              </Text>
            </TouchableOpacity>
          ))}
        </ScrollView>
      </View>
      <MapView
        provider={PROVIDER_GOOGLE}
        style={styles.map}
        region={mapRegion}
        showsUserLocation={true}
        showsMyLocationButton={true}
        onRegionChangeComplete={setMapRegion}
      >
        {/* Search radius circle */}
        {userLocation && (
          <Circle
            center={userLocation}
            radius={searchRadius}
            strokeColor="rgba(0, 122, 255, 0.5)"
            fillColor="rgba(0, 122, 255, 0.1)"
            strokeWidth={2}
          />
        )}
        {/* Charging Station Markers */}
        {data?.chargingStations?.map(station => (
          <Marker
            key={station.id}
            coordinate={{
              latitude: station.location.latitude,
              longitude: station.location.longitude,
            }}
            title={station.name}
            description={`${station.connectors.filter(c => c.status === 'AVAILABLE').length} available`}
            pinColor={getMarkerColor(station)}
            onPress={() => setSelectedStation(station)}
          />
        ))}
      </MapView>
      {/* Station Details Bottom Sheet */}
      {selectedStation && (
        <View style={styles.bottomSheet}>
          <ScrollView style={styles.bottomSheetContent}>
            <View style={styles.bottomSheetHeader}>
              <Text style={styles.stationName}>{selectedStation.name}</Text>
              <TouchableOpacity
                style={styles.closeButton}
                onPress={() => setSelectedStation(null)}
              >
                <Text style={styles.closeButtonText}>Γ</Text>
              </TouchableOpacity>
            </View>
            
            <Text style={styles.address}>{selectedStation.location.address}</Text>
            
            {userLocation && (
              <Text style={styles.distance}>
                π {getDistanceText(selectedStation)} away
              </Text>
            )}
            
            <Text style={styles.status}>
              Status: <Text style={styles.statusValue}>{selectedStation.status}</Text>
            </Text>
            
            <View style={styles.connectorsSection}>
              <Text style={styles.connectorsTitle}>
                Available Connectors ({selectedStation.connectors.filter(c => c.status === 'AVAILABLE').length}):
              </Text>
              
              {selectedStation.connectors
                .filter(connector => connector.status === 'AVAILABLE')
                .map(connector => (
                  <TouchableOpacity
                    key={connector.id}
                    style={styles.connectorButton}
                    onPress={() => handleStartCharging(selectedStation.id, connector.id)}
                    disabled={starting}
                  >
                    <Text style={styles.connectorText}>
                      π {connector.type} - {connector.power}kW
                    </Text>
                    {starting && <Text style={styles.startingText}>Starting...</Text>}
                  </TouchableOpacity>
                ))
              }
              
              {selectedStation.connectors.filter(c => c.status === 'AVAILABLE').length === 0 && (
                <View style={styles.noConnectorsContainer}>
                  <Text style={styles.noConnectors}>β οΈ No available connectors</Text>
                  <Text style={styles.noConnectorsSubtext}>All connectors are currently in use</Text>
                </View>
              )}
            </View>
            {/* Navigation Button */}
            <TouchableOpacity
              style={styles.navigationButton}
              onPress={() => {
                // This would typically open the native maps app
                Alert.alert('Navigation', 'This would open your preferred navigation app');
              }}
            >
              <Text style={styles.navigationButtonText}>πΊοΈ Get Directions</Text>
            </TouchableOpacity>
          </ScrollView>
        </View>
      )}
      {/* Floating Action Buttons */}
      <View style={styles.floatingButtons}>
        {userLocation && (
          <TouchableOpacity
            style={styles.centerButton}
            onPress={() => setMapRegion({
              ...userLocation,
              latitudeDelta: 0.02,
              longitudeDelta: 0.02,
            })}
          >
            <Text style={styles.centerButtonText}>π</Text>
          </TouchableOpacity>
        )}
        
        <TouchableOpacity
          style={styles.refreshButton}
          onPress={() => refetch()}
        >
          <Text style={styles.refreshButtonText}>π</Text>
        </TouchableOpacity>
      </View>
      {/* Stats Bar */}
      <View style={styles.statsBar}>
        <Text style={styles.statsText}>
          Found {data?.chargingStations?.length || 0} stations within {searchRadius < 1000 ? `${searchRadius}m` : `${searchRadius/1000}km`}
        </Text>
      </View>
    </View>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  centered: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  map: {
    flex: 1,
  },
  radiusControls: {
    position: 'absolute',
    top: 10,
    left: 0,
    right: 0,
    zIndex: 1,
    paddingHorizontal: 15,
  },
  radiusButton: {
    backgroundColor: 'white',
    paddingHorizontal: 15,
    paddingVertical: 8,
    borderRadius: 20,
    marginHorizontal: 5,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5,
  },
  activeRadiusButton: {
    backgroundColor: '#007AFF',
  },
  radiusButtonText: {
    fontWeight: 'bold',
    color: '#333',
  },
  activeRadiusButtonText: {
    color: 'white',
  },
  retryButton: {
    backgroundColor: '#007AFF',
    padding: 15,
    borderRadius: 8,
    marginTop: 20,
  },
  retryText: {
    color: 'white',
    fontWeight: 'bold',
  },
  bottomSheet: {
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: 'white',
    borderTopLeftRadius: 20,
    borderTopRightRadius: 20,
    maxHeight: height * 0.6,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: -2 },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5,
  },
  bottomSheetContent: {
    padding: 20,
  },
  bottomSheetHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 10,
  },
  stationName: {
    fontSize: 20,
    fontWeight: 'bold',
    flex: 1,
    color: '#333',
  },
  closeButton: {
    width: 30,
    height: 30,
    borderRadius: 15,
    backgroundColor: '#f0f0f0',
    justifyContent: 'center',
    alignItems: 'center',
  },
  closeButtonText: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#666',
  },
  address: {
    color: '#666',
    marginBottom: 8,
    fontSize: 14,
  },
  distance: {
    color: '#007AFF',
    fontWeight: 'bold',
    marginBottom: 8,
    fontSize: 14,
  },
  status: {
    marginBottom: 15,
    fontSize: 14,
  },
  statusValue: {
    fontWeight: 'bold',
    color: '#34C759',
  },
  connectorsSection: {
    marginBottom: 15,
  },
  connectorsTitle: {
    fontWeight: 'bold',
    marginBottom: 10,
    fontSize: 16,
    color: '#333',
  },
  connectorButton: {
    backgroundColor: '#007AFF',
    padding: 15,
    borderRadius: 8,
    marginVertical: 3,
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  connectorText: {
    color: 'white',
    fontWeight: 'bold',
    flex: 1,
  },
  startingText: {
    color: 'white',
    fontStyle: 'italic',
  },
  noConnectorsContainer: {
    padding: 20,
    alignItems: 'center',
  },
  noConnectors: {
    textAlign: 'center',
    color: '#FF3B30',
    fontWeight: 'bold',
    fontSize: 16,
  },
  noConnectorsSubtext: {
    textAlign: 'center',
    color: '#666',
    fontSize: 12,
    marginTop: 5,
  },
  navigationButton: {
    backgroundColor: '#34C759',
    padding: 15,
    borderRadius: 8,
    marginTop: 10,
  },
  navigationButtonText: {
    color: 'white',
    textAlign: 'center',
    fontWeight: 'bold',
    fontSize: 16,
  },
  floatingButtons: {
    position: 'absolute',
    right: 20,
    top: 80,
  },
  centerButton: {
    width: 50,
    height: 50,
    borderRadius: 25,
    backgroundColor: 'white',
    justifyContent: 'center',
    alignItems: 'center',
    marginBottom: 10,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5,
  },
  centerButtonText: {
    fontSize: 24,
  },
  refreshButton: {
    width: 50,
    height: 50,
    borderRadius: 25,
    backgroundColor: 'white',
    justifyContent: 'center',
    alignItems: 'center',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5,
  },
  refreshButtonText: {
    fontSize: 20,
  },
  statsBar: {
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: 'rgba(0, 0, 0, 0.8)',
    padding: 10,
  },
  statsText: {
    color: 'white',
    textAlign: 'center',
    fontSize: 12,
  },
});
Enhanced Charging Stations Screen with Map
Create src/screens/ChargingStationsScreen.js:
import React, { useState, useEffect } from 'react';
import {
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
  Alert,
  Dimensions,
  ScrollView,
} from 'react-native';
import MapView, { Marker, PROVIDER_GOOGLE } from 'react-native-maps';
import { useQuery, useMutation } from '@apollo/client';
import { GET_CHARGING_STATIONS, START_CHARGING_SESSION } from '../graphql/operations';
import { LocationService } from '../services/locationService';
const { width, height } = Dimensions.get('window');
export default function ChargingStationsScreen() {
  const [userLocation, setUserLocation] = useState(null);
  const [selectedStation, setSelectedStation] = useState(null);
  const [mapRegion, setMapRegion] = useState({
    latitude: 37.78825,
    longitude: -122.4324,
    latitudeDelta: 0.0922,
    longitudeDelta: 0.0421,
  });
  const { data, loading, error, refetch } = useQuery(GET_CHARGING_STATIONS, {
    variables: {
      filter: {
        availability: 'AVAILABLE',
        // Add radius filter based on user location
        ...(userLocation && {
          location: {
            latitude: userLocation.latitude,
            longitude: userLocation.longitude,
            radius: 10000, // 10km radius
          }
        })
      }
    }
  });
  const [startSession, { loading: starting }] = useMutation(START_CHARGING_SESSION);
  useEffect(() => {
    initializeLocation();
  }, []);
  const initializeLocation = async () => {
    const hasPermission = await LocationService.requestLocationPermission();
    
    if (hasPermission) {
      try {
        const location = await LocationService.getCurrentPosition();
        setUserLocation(location);
        setMapRegion({
          ...location,
          latitudeDelta: 0.01,
          longitudeDelta: 0.01,
        });
        
        // Refetch stations with new location
        refetch();
      } catch (error) {
        Alert.alert('Location Error', 'Could not get your current location');
      }
    } else {
      Alert.alert('Permission Denied', 'Location permission is required to find nearby stations');
    }
  };
  const handleStartCharging = async (stationId, connectorId) => {
    try {
      const result = await startSession({
        variables: {
          input: {
            chargingStationId: stationId,
            connectorId: connectorId,
            driverId: 'current-driver-id',
          }
        }
      });
      if (result.data?.startChargingSession?.success) {
        Alert.alert('Success', 'Charging session started successfully');
        setSelectedStation(null);
      } else {
        const error = result.data?.startChargingSession?.errors?.[0]?.message;
        Alert.alert('Error', error || 'Failed to start charging session');
      }
    } catch (error) {
      Alert.alert('Error', 'Network error occurred');
    }
  };
  const getMarkerColor = (station) => {
    const availableConnectors = station.connectors.filter(c => c.status === 'AVAILABLE').length;
    if (availableConnectors === 0) return '#FF3B30'; // Red for no availability
    if (availableConnectors <= 2) return '#FF9500'; // Orange for limited availability
    return '#34C759'; // Green for good availability
  };
  const getDistanceText = (station) => {
    if (!userLocation) return '';
    const distance = LocationService.calculateDistance(
      userLocation.latitude,
      userLocation.longitude,
      station.location.latitude,
      station.location.longitude
    );
    return distance < 1 ? `${Math.round(distance * 1000)}m` : `${distance.toFixed(1)}km`;
  };
  if (loading && !data) {
    return (
      <View style={styles.centered}>
        <Text>Loading charging stations...</Text>
      </View>
    );
  }
  if (error) {
    return (
      <View style={styles.centered}>
        <Text>Error loading stations</Text>
        <TouchableOpacity style={styles.retryButton} onPress={() => refetch()}>
          <Text style={styles.retryText}>Retry</Text>
        </TouchableOpacity>
      </View>
    );
  }
  return (
    <View style={styles.container}>
      <MapView
        provider={PROVIDER_GOOGLE}
        style={styles.map}
        region={mapRegion}
        showsUserLocation={true}
        showsMyLocationButton={true}
        onRegionChangeComplete={setMapRegion}
      >
        {/* User Location Marker */}
        {userLocation && (
          <Marker
            coordinate={userLocation}
            title="Your Location"
            pinColor="blue"
          />
        )}
        {/* Charging Station Markers */}
        {data?.chargingStations?.map(station => (
          <Marker
            key={station.id}
            coordinate={{
              latitude: station.location.latitude,
              longitude: station.location.longitude,
            }}
            title={station.name}
            description={`${station.connectors.filter(c => c.status === 'AVAILABLE').length} available connectors`}
            pinColor={getMarkerColor(station)}
            onPress={() => setSelectedStation(station)}
          />
        ))}
      </MapView>
      {/* Station Details Bottom Sheet */}
      {selectedStation && (
        <View style={styles.bottomSheet}>
          <ScrollView style={styles.bottomSheetContent}>
            <View style={styles.bottomSheetHeader}>
              <Text style={styles.stationName}>{selectedStation.name}</Text>
              <TouchableOpacity
                style={styles.closeButton}
                onPress={() => setSelectedStation(null)}
              >
                <Text style={styles.closeButtonText}>Γ</Text>
              </TouchableOpacity>
            </View>
            
            <Text style={styles.address}>{selectedStation.location.address}</Text>
            
            {userLocation && (
              <Text style={styles.distance}>
                Distance: {getDistanceText(selectedStation)}
              </Text>
            )}
            
            <Text style={styles.status}>Status: {selectedStation.status}</Text>
            
            <View style={styles.connectorsSection}>
              <Text style={styles.connectorsTitle}>Available Connectors:</Text>
              {selectedStation.connectors
                .filter(connector => connector.status === 'AVAILABLE')
                .map(connector => (
                  <TouchableOpacity
                    key={connector.id}
                    style={styles.connectorButton}
                    onPress={() => handleStartCharging(selectedStation.id, connector.id)}
                    disabled={starting}
                  >
                    <Text style={styles.connectorText}>
                      {connector.type} - {connector.power}kW
                    </Text>
                  </TouchableOpacity>
                ))
              }
              
              {selectedStation.connectors.filter(c => c.status === 'AVAILABLE').length === 0 && (
                <Text style={styles.noConnectors}>No available connectors</Text>
              )}
            </View>
          </ScrollView>
        </View>
      )}
      {/* Floating Action Button to Center on User */}
      {userLocation && (
        <TouchableOpacity
          style={styles.centerButton}
          onPress={() => setMapRegion({
            ...userLocation,
            latitudeDelta: 0.01,
            longitudeDelta: 0.01,
          })}
        >
          <Text style={styles.centerButtonText}>π</Text>
        </TouchableOpacity>
      )}
    </View>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  centered: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  map: {
    flex: 1,
  },
  retryButton: {
    backgroundColor: '#007AFF',
    padding: 15,
    borderRadius: 8,
    marginTop: 20,
  },
  retryText: {
    color: 'white',
    fontWeight: 'bold',
  },
  bottomSheet: {
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: 'white',
    borderTopLeftRadius: 20,
    borderTopRightRadius: 20,
    maxHeight: height * 0.5,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: -2 },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5,
  },
  bottomSheetContent: {
    padding: 20,
  },
  bottomSheetHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 10,
  },
  stationName: {
    fontSize: 20,
    fontWeight: 'bold',
    flex: 1,
  },
  closeButton: {
    width: 30,
    height: 30,
    borderRadius: 15,
    backgroundColor: '#f0f0f0',
    justifyContent: 'center',
    alignItems: 'center',
  },
  closeButtonText: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#666',
  },
  address: {
    color: '#666',
    marginBottom: 5,
  },
  distance: {
    color: '#007AFF',
    fontWeight: 'bold',
    marginBottom: 5,
  },
  status: {
    marginBottom: 15,
  },
  connectorsSection: {
    marginBottom: 20,
  },
  connectorsTitle: {
    fontWeight: 'bold',
    marginBottom: 10,
    fontSize: 16,
  },
  connectorButton: {
    backgroundColor: '#007AFF',
    padding: 12,
    borderRadius: 8,
    marginVertical: 3,
  },
  connectorText: {
    color: 'white',
    textAlign: 'center',
    fontWeight: 'bold',
  },
  noConnectors: {
    textAlign: 'center',
    color: '#666',
    fontStyle: 'italic',
    padding: 20,
  },
  centerButton: {
    position: 'absolute',
    top: 60,
    right: 20,
    width: 50,
    height: 50,
    borderRadius: 25,
    backgroundColor: 'white',
    justifyContent: 'center',
    alignItems: 'center',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5,
  },
  centerButtonText: {
    fontSize: 24,
  },
});
Step 5: Push Notification Service
Create src/services/notificationService.js:
import PushNotification from 'react-native-push-notification';
import { Platform } from 'react-native';
export const setupPushNotifications = () => {
  PushNotification.configure({
    onNotification: function(notification) {
      console.log('Notification received:', notification);
    },
    requestPermissions: Platform.OS === 'ios',
  });
  // Create notification channels for Android
  if (Platform.OS === 'android') {
    PushNotification.createChannel(
      {
        channelId: 'charging-sessions',
        channelName: 'Charging Sessions',
        channelDescription: 'Notifications for charging session updates',
        soundName: 'default',
        importance: 4,
        vibrate: true,
      },
      (created) => console.log(`Channel created: ${created}`)
    );
  }
};
export const showNotification = ({ title, message, data = {} }) => {
  PushNotification.localNotification({
    channelId: 'charging-sessions',
    title,
    message,
    userInfo: data,
    playSound: true,
    soundName: 'default',
    vibrate: true,
  });
};
Step 6: App Configuration
Update App.js (Root)
import React from 'react';
import App from './src/App';
export default App;
Configure Permissions
Android (android/app/src/main/AndroidManifest.xml)
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Add Google Maps API key -->
<application>
  <meta-data
    android:name="com.google.android.geo.API_KEY"
    android:value="YOUR_GOOGLE_MAPS_API_KEY_HERE"/>
</application>
iOS (ios/YourApp/Info.plist)
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access to find nearby charging stations</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs location access to find nearby charging stations</string>
Google Maps Setup
Get Google Maps API Key:
- Go to Google Cloud Console
 - Enable Maps SDK for Android/iOS
 - Create API key and restrict it appropriately
 
Configure for iOS (ios/Podfile):
# Add to your Podfile
pod 'GoogleMaps'
pod 'Google-Maps-iOS-Utils'
- Run pod install:
 
cd ios && pod install && cd ..
Step 7: Running and Testing
Start the Development Server
npm start
Run on Device/Emulator
# iOS
npm run ios
# Android
npm run android
Key Features Implemented
- Authentication: Secure API key-based authentication
 - Real-time Updates: GraphQL subscriptions for live session monitoring
 - Remote Control: Start and stop charging sessions remotely
 - Push Notifications: Local notifications for session completion
 - Interactive Map: Native map with user location and charging station markers
 - Location Services: GPS tracking and distance calculations
 - Station Discovery: Browse and filter available charging stations by radius
 - Session Management: View active sessions with real-time status
 - Visual Indicators: Color-coded markers showing station availability
 - Search Radius Control: Adjustable search area from 1km to 20km
 
Best Practices
- Error Handling: Comprehensive error handling for network issues
 - Loading States: User-friendly loading indicators
 - Offline Support: Cache important data for offline viewing
 - Security: Secure storage of API credentials
 - Performance: Efficient polling and subscription management
 - Location Privacy: Request permissions appropriately and handle denials gracefully
 - Map Performance: Optimize marker rendering for large datasets
 - Battery Optimization: Efficient location tracking with appropriate distance filters
 
Map-Specific Features
Color-Coded Markers
- π’ Green: Good availability (3+ connectors)
 - π Orange: Limited availability (1-2 connectors)
 - π΄ Red: No availability (0 connectors)
 
Location Features
- User Location Tracking: Real-time GPS positioning
 - Distance Calculations: Accurate distance measurements to stations
 - Search Radius: Adjustable from 1km to 20km
 - Auto-refresh: Location updates trigger station data refresh
 
Map Controls
- Center Button: Quick return to user location
 - Refresh Button: Manual data refresh
 - Radius Picker: Easy search area adjustment
 - Stats Bar: Shows number of stations found
 
Next Steps
- Add route planning to selected charging stations
 - Implement real-time availability updates on map
 - Add clustering for better performance with many markers
 - Enhance offline support with cached map tiles
 - Implement advanced filtering (connector type, power level, pricing)
 - Add navigation integration with native maps apps
 - Include augmented reality features for station identification
 - Add charging history and analytics dashboard
 - Implement favorite stations and trip planning
 - Add voice guidance and accessibility features
 
Troubleshooting Map Issues
Common Problems and Solutions
Map not showing:
- Verify Google Maps API key is configured
 - Check that Maps SDK is enabled in Google Cloud Console
 
Location not working:
- Ensure location permissions are granted
 - Check that location services are enabled on device
 
Markers not appearing:
- Verify GraphQL queries return valid coordinates
 - Check that latitude/longitude values are valid numbers
 
Performance issues:
- Implement marker clustering for large datasets
 - Use appropriate map region bounds
 - Optimize GraphQL queries with proper filtering
 
This tutorial provides a solid foundation for building a production-ready driver app with the InControl API. Remember to test thoroughly and handle edge cases for the best user experience.