# Location Sharing

This cookbook shows how to add static and live location sharing to a React Native chat app using Stream Chat.

There are two types of location sharing:

- **Static Location**: A one-time snapshot of the user's current position.
- **Live Location**: A real-time share that updates the user's position for a chosen duration.

## Best Practices

- Request location permissions just-in-time and explain why you need them.
- Provide clear UI to stop live sharing and surface remaining time.
- Avoid high-frequency updates; use reasonable intervals to save battery.
- Handle permission denial gracefully by falling back to static sharing.
- Store location metadata on the message to render without extra lookups.

## Prerequisites

The SDK handles location messages and updates, but your app must handle device location tracking. You need a working `chatClient` and a `Channel` screen with `MessageList` and `MessageComposer` in place.

Use the [Sample App](https://github.com/GetStream/stream-chat-react-native/tree/develop/examples/SampleApp) or [Expo Messaging app](https://github.com/GetStream/stream-chat-react-native/tree/develop/examples/ExpoMessaging) as a full reference implementation.

## Setup

### Installing Dependencies

**Native CLI**

```bash
yarn add @react-native-community/geolocation
yarn add react-native-maps
```

- `@react-native-community/geolocation` watches the user's current location and provides coordinates to send in a message.
- `react-native-maps` renders location data on native Apple Maps (iOS) and Google Maps (Android).

**Expo**

```bash
yarn add expo-location
yarn add react-native-maps
```

- `expo-location` watches the user's current location and provides coordinates to send in a message.
- `react-native-maps` renders location data on native Apple Maps (iOS) and Google Maps (Android).

### Configuring Location Permissions

**Native CLI**

**iOS**

Include `NSLocationWhenInUseUsageDescription` and `NSLocationAlwaysAndWhenInUseUsageDescription` in `Info.plist` to enable geolocation when using the app.

**Android**

Add the following permissions to `AndroidManifest.xml`:

```xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
```

**Android: Google Maps API Key**

On Android, [Google Maps](https://developers.google.com/maps/documentation/) is required, which needs an [API key for the Android SDK](https://developers.google.com/maps/documentation/android-sdk/signup). iOS uses the native Apple Maps implementation and does not need an API key.

Add your API key to `android/app/src/main/AndroidManifest.xml`:

```xml
<application>
   <meta-data
     android:name="com.google.android.geo.API_KEY"
     android:value="Your Google maps API Key Here"/>
</application>
```

**Expo**

Configure location permissions in your `app.json` or `app.config.js`:

```json
{
  "expo": {
    "plugins": [
      [
        "expo-location",
        {
          "locationAlwaysAndWhenInUsePermission": "Allow $(PRODUCT_NAME) to use your location."
        }
      ]
    ]
  }
}
```

Alternatively, follow the [Expo Location guide](https://docs.expo.dev/versions/latest/sdk/location/#example-appjson-with-config-plugin).

**Android: Google Maps API Key**

Add the Google Maps API key to your `app.json` or `app.config.js`:

```json
{
  "expo": {
    "android": {
      "config": {
        "googleMaps": {
          "apiKey": "Your Google maps API Key Here"
        }
      }
    }
  }
}
```

Alternatively, follow the [Expo MapView deploy guide](https://docs.expo.dev/versions/latest/sdk/map-view/#deploy-app-with-google-maps).

## Sending a Location Message

To send a location message, set data on `messageComposer.locationComposer` and call `sendLocation`. The difference between static and live is whether you include a `durationMs`.

### Static Location

A static location sends the user's current coordinates as a one-time share:

```ts
const sendStaticLocation = async () => {
  const { latitude, longitude } = await getCurrentLocation();
  await messageComposer.locationComposer.setData({
    latitude,
    longitude,
  });
  await messageComposer.sendLocation();
};
```

### Live Location

A live location includes a duration. The SDK watches for location changes and updates the message automatically:

```ts
const sendLiveLocation = async () => {
  const { latitude, longitude } = await getCurrentLocation();
  await messageComposer.locationComposer.setData({
    durationMs: 600000, // 10 minutes
    latitude,
    longitude,
  });
  await messageComposer.sendLocation();
};
```

![Preview of create location modal](https://getstream.io/docs-assets/images/124f747d97e9.png)

Full example implementations of the create-location modal:

- [Expo](https://github.com/GetStream/stream-chat-react-native/blob/develop/examples/ExpoMessaging/components/LocationSharing/CreateLocationModal.tsx)
- [Native CLI](https://github.com/GetStream/stream-chat-react-native/blob/develop/examples/SampleApp/src/components/LocationSharing/CreateLocationModal.tsx)

### Adding a Location Button to the Input

Override the `InputButtons` component via `WithComponents` to add a button that opens the location-sharing modal:

```tsx
import React, { useState } from "react";
import { Pressable, StyleSheet } from "react-native";
import {
  InputButtons as DefaultInputButtons,
  InputButtonsProps,
} from "stream-chat-expo";
import { ShareLocationIcon } from "../icons/ShareLocationIcon";
import { LiveLocationCreateModal } from "./LocationSharing/CreateLocationModal";

const InputButtons = (props: InputButtonsProps) => {
  const [modalVisible, setModalVisible] = useState(false);

  const onRequestClose = () => {
    setModalVisible(false);
  };

  const onOpenModal = () => {
    setModalVisible(true);
  };

  return (
    <>
      <DefaultInputButtons {...props} hasCommands={false} />
      <Pressable style={styles.liveLocationButton} onPress={onOpenModal}>
        <ShareLocationIcon />
      </Pressable>
      <LiveLocationCreateModal
        visible={modalVisible}
        onRequestClose={onRequestClose}
      />
    </>
  );
};

const styles = StyleSheet.create({
  liveLocationButton: {
    paddingLeft: 5,
  },
});

export default InputButtons;
```

Provide the custom component through `WithComponents`:

```tsx
import { Channel, WithComponents } from "stream-chat-expo";
import InputButtons from "./InputButtons";

<WithComponents overrides={{ InputButtons }}>
  <Channel channel={channel}>{/* ... */}</Channel>
</WithComponents>;
```

## Rendering the Location in a Message

When a message includes location data, the `shared_location` field contains latitude, longitude, and other details. The SDK does not ship a default map UI, so you need to build a custom component and provide it via the `MessageLocation` override in `WithComponents`.

Use `react-native-maps` to render the map with the shared coordinates:

![Preview of message location](https://getstream.io/docs-assets/images/0c04a186c806.png)

Example `MessageLocation` implementations:

- [Expo](https://github.com/GetStream/stream-chat-react-native/blob/develop/examples/ExpoMessaging/components/LocationSharing/MessageLocation.tsx)
- [Native CLI](https://github.com/GetStream/stream-chat-react-native/blob/develop/examples/SampleApp/src/components/LocationSharing/MessageLocation.tsx)

## Managing Live Location Updates

An app can have multiple active live locations across channels. Use `client.getSharedLocations()` to fetch all active live locations for the current user.

The `LiveLocationManager` class from `stream-chat` coordinates live location updates across your app. The SDK exposes a `LiveLocationManagerProvider` that supplies an instance and handles event registration/deregistration automatically.

Wrap your app with `LiveLocationManagerProvider` and pass a `watchLocation` function that watches device location:

**Native CLI**

```ts
import Geolocation from "@react-native-community/geolocation";

type LocationHandler = (value: { latitude: number; longitude: number }) => void;

export const watchLocation = (handler: LocationHandler) => {
  let watchId: number | null = null;
  watchId = Geolocation.watchPosition(
    (position) => {
      const { latitude, longitude } = position.coords;
      handler({ latitude, longitude });
    },
    (error) => {
      console.warn("Error watching location:", error);
    },
    {
      enableHighAccuracy: true,
      timeout: 20000,
      maximumAge: 1000,
      interval: 2000, // android only
    },
  );

  return () => {
    if (watchId) {
      Geolocation.clearWatch(watchId);
    }
  };
};
```

**Expo**

```ts
import * as Location from "expo-location";

export type LocationHandler = (value: {
  latitude: number;
  longitude: number;
}) => void;

export const watchLocation = (handler: LocationHandler) => {
  let subscription: Location.LocationSubscription | null = null;
  Location.watchPositionAsync(
    {
      accuracy: Location.Accuracy.High,
      distanceInterval: 0,
      // Android only: these option are ignored on iOS
      timeInterval: 2000,
    },
    (location) => {
      const { latitude, longitude } = location.coords;
      handler({ latitude, longitude });
    },
    (error) => {
      console.warn("Error watching location:", error);
    },
  ).then((sub) => {
    subscription = sub;
  });

  return () => {
    subscription?.remove();
  };
};
```

Then wrap your app:

```tsx
import { LiveLocationManagerProvider } from "stream-chat-react-native";

const App = () => {
  return (
    <LiveLocationManagerProvider watchLocation={watchLocation}>
      {/* Your app components */}
    </LiveLocationManagerProvider>
  );
};
```

> **Tip:** You can optionally pass `getDeviceId` (a function that returns a string) to identify the device sharing the location.

## Navigating to a Full-Screen Map

When the user taps a location message, navigate to a detail screen that shows a full-screen map. Use the `onPressMessage` prop on `Channel`:

**Native CLI**

```tsx
const App = () => {
  const onPressMessage: NonNullable<
    React.ComponentProps<typeof Channel>["onPressMessage"]
  > = (payload) => {
    const { message, defaultHandler, emitter } = payload;
    const { shared_location } = message ?? {};
    if (emitter === "messageContent" && shared_location) {
      navigation.navigate("MapScreen", shared_location);
    }
    defaultHandler?.();
  };

  return <Channel onPressMessage={onPressMessage}></Channel>;
};
```

**Expo**

```tsx
const App = () => {
  const onPressMessage: NonNullable<
    React.ComponentProps<typeof Channel>["onPressMessage"]
  > = (payload) => {
    const { message, defaultHandler, emitter } = payload;
    const { shared_location } = message;
    if (emitter === "messageContent" && shared_location) {
      const params = Object.entries(shared_location)
        .map(([key, value]) => `${key}=${value}`)
        .join("&");
      router.push(`/map/${message.id}?${params}`);
    }
    defaultHandler?.();
  };

  return <Channel onPressMessage={onPressMessage}></Channel>;
};
```

## Building the Map Detail Screen

This screen displays the shared location on a full-screen map. For live locations, it shows a stop-sharing button and the remaining time. For ended live locations, it shows the final position.

| Current Location                                                                                                            | Live Location                                                                                                            | Live Location Ended                                                                                                            |
| --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| ![Preview of chat screen](https://getstream.io/docs-assets/images/fb853d0aa95e.png) | ![Preview of chat screen](https://getstream.io/docs-assets/images/e96287263586.png) | ![Preview of chat screen](https://getstream.io/docs-assets/images/0b501cb16620.png) |

To keep the map marker up to date while a live location is active, listen for backend events. The key event is:

- `message.updated` — Fired when the message's `shared_location` changes.

Use the `useHandleLiveLocationEvents` hook with `messageId`, `channel`, and an `onLocationUpdate` callback. The callback fires on every `message.updated` event and can update the map marker position.

Example `MapScreen` implementations:

- [Expo](https://github.com/GetStream/stream-chat-react-native/blob/develop/examples/ExpoMessaging/app/map/%5Bid%5D.tsx)
- [Native CLI](https://github.com/GetStream/stream-chat-react-native/blob/develop/examples/SampleApp/src/screens/MapScreen.tsx)

## Stopping Live Location Sharing

To stop an active live location share, call `stopLiveLocationSharing` on the channel with the current location response:

```tsx
const stopSharingLiveLocation = async () => {
  if (!locationResponse) {
    return;
  }
  await channel?.stopLiveLocationSharing(locationResponse);
};
```

The message updates automatically once sharing stops, and the map detail screen reflects the final position.

## Sample Code

Complete implementations are available in the sample apps:

- [Expo Messaging](https://github.com/GetStream/stream-chat-react-native/blob/develop/examples/ExpoMessaging)
- [Native CLI Sample App](https://github.com/GetStream/stream-chat-react-native/blob/develop/examples/SampleApp)

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/guides/location-sharing/](https://getstream.io/chat/docs/sdk/react-native/guides/location-sharing/).