# Location sharing

> **Info:** This is release candidate documentation for **Stream Chat React Native SDK v10**. For the latest stable version, see the [latest version (v9)](https://getstream.io/chat/docs/sdk/react-native/).

Location sharing sends where a user is, either once or live for a while. This page covers what the SDK provides and what your app adds: the share UI, the map in the message, and the device location that keeps a live share current.

## What users get

The SDK has no location UI of its own. It doesn't read the device's location, and it ships no map. With the pieces this page describes, an app gives users this:

The user taps a location button in the attachment picker. Their app finds their position and asks whether to share it once or live, for 15 minutes or an hour. Sharing sends the location as a message of its own, which shows as a map with a pin. A live location reads "Live until" and the end time, and the user's own has a Stop sharing button. When the time is up or they stop, it reads that sharing has ended. While their live share runs, their app keeps sending their position, so the pin moves for everyone in the channel.

The SDK renders one part by itself, the channel list's preview. A channel whose latest message is a location shows "Location", or "Live Location" while the share runs.

## How it works across the layers

- **State.** The composer's `locationComposer` holds the location being shared. A shared location is stored with its message as `message.shared_location`, not in `message.attachments`. A `LiveLocationManager` keeps the user's live shares updated.
- **UI.** `LiveLocationManagerProvider` creates the manager. The `MessageLocation` override key renders a location in a message, and has no default. `useHandleLiveLocationEvents` follows one shared location. The share UI is your app's.
- **Layout.** Not involved. A full-screen map is a screen of your own navigation.

### Sharing a location

1. Your UI reads the device's position, for example with `@react-native-community/geolocation` or `expo-location`.
2. It calls `LocationComposer.setData({ latitude, longitude, durationMs })`. `durationMs` is set for a live location only.
3. It calls `MessageComposer.sendLocation()`, which sends a message carrying only the location, through `Channel.sendSharedLocation()`. In a thread's composer, it does nothing.
4. When the share fails, `MessageComposer.sendLocation()` publishes an `api:location:create:failed` notification, "Failed to share location", and throws.

A live location is valid only for at least `location.minShareDurationMs`, one minute by default. `MessageComposer.send()` also puts a location that `LocationComposer.setData()` set on the message being written, but the SDK's composer shows no preview of it. [Message composer](https://getstream.io/chat/docs/sdk/react-native/v10/state/writing-messages/message-composer/#location) explains `locationComposer`.

### Keeping a live location current

`LiveLocationManager` sends the device's new positions for every live location the user is sharing, including shares started on another of the user's devices. `LiveLocationManagerProvider` creates one:

1. On mount, the manager loads the user's active live locations with `client.getUserLiveLocations()`.
2. When the user starts a new live share, the `live_location_sharing.started` event adds it. Stopping it or deleting its message removes it.
3. While at least one share is active, the manager calls your `watchLocation` function. Each position it reports is sent to every active share with `client.updateLiveLocation()`, at most once every `liveLocationManager.minUpdateThrottleMs` (3 seconds by default).
4. Each share is dropped when its end time passes. `Channel.stopLiveLocationSharing({ message_id })` ends one early.

The other clients receive the new position in the message's `message.updated` event. The message list re-renders the message when its `shared_location` coordinates or end time change.

## Turning it on or off

Location sharing is on by default. Two sides decide:

- **The client:** `messageComposer.location.enabled`, `true` by default.
- **The server:** the channel type's `shared_locations` setting.

To turn it off in every composer:

```ts
client.config.set({
  messageComposer: { location: { enabled: false } },
});
```

With it off, `LocationComposer.setData()` does nothing, so nothing is sent. Locations work in channels only, not in threads. [Configuration](https://getstream.io/chat/docs/sdk/react-native/v10/state/state-and-entities/configuration/#who-decides-what) lists every setting both sides decide.

Reading the device's location needs a location package and its permissions, which aren't part of the SDK's installation. The [Location sharing UI cookbook](https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/app/location-sharing/) sets them up, with Google Maps for Android.

### Live locations need the manager

Without a `LiveLocationManager`, live shares are created, but their position never changes. Render `LiveLocationManagerProvider` once, inside `Chat` and after the user is connected, and pass it how to watch the position:

**RN CLI**

```tsx title="LiveLocation.tsx"
import Geolocation from "@react-native-community/geolocation";
import type { PropsWithChildren } from "react";
import type { WatchLocation } from "stream-chat";
import { LiveLocationManagerProvider } from "stream-chat-react-native";

// Declared once: a new function on every render would create a new manager.
const watchLocation: WatchLocation = (handler) => {
  const watchId = Geolocation.watchPosition(
    ({ coords }) =>
      handler({ latitude: coords.latitude, longitude: coords.longitude }),
    (error) => console.warn("Watching the location failed", error),
    { enableHighAccuracy: true },
  );

  return () => Geolocation.clearWatch(watchId);
};

export const LiveLocation = ({ children }: PropsWithChildren) => (
  <LiveLocationManagerProvider watchLocation={watchLocation}>
    {children}
  </LiveLocationManagerProvider>
);
```

**Expo**

```tsx title="LiveLocation.tsx"
import * as Location from "expo-location";
import type { PropsWithChildren } from "react";
import type { WatchLocation } from "stream-chat";
import { LiveLocationManagerProvider } from "stream-chat-expo";

// Declared once: a new function on every render would create a new manager.
const watchLocation: WatchLocation = (handler) => {
  let subscription: Location.LocationSubscription | undefined;
  let stopped = false;

  Location.watchPositionAsync(
    { accuracy: Location.Accuracy.High },
    ({ coords }) =>
      handler({ latitude: coords.latitude, longitude: coords.longitude }),
  ).then((watch) => {
    if (stopped) watch.remove();
    else subscription = watch;
  });

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

export const LiveLocation = ({ children }: PropsWithChildren) => (
  <LiveLocationManagerProvider watchLocation={watchLocation}>
    {children}
  </LiveLocationManagerProvider>
);
```

`watchLocation` receives a handler to call with each position, and returns a function that stops watching. To report less often, report on a timer instead. The manager is created with the client's connected user, and throws when the client has no user yet, so render the provider only once `client.connectUser()` has finished, as the client from `useCreateChatClient` does. Keep it mounted while the user is signed in: it stops sending positions when it unmounts. Watching the location while the app is in the background needs the location package's background mode.

The provider also takes `getDeviceId`, which sets the manager's `deviceId`. By default it's `react-native-` with the platform and the user id. The device id stored on a new share comes from `messageComposer.location.getDeviceId` instead, a random id by default.

## Customizing

### Show a location in a message

`MessageLocation` receives the `message`. This one shows the position on a map with [`react-native-maps`](https://github.com/react-native-maps/react-native-maps), and under it the state of a live location, with a Stop sharing button on the user's own:

```tsx
import { Pressable, StyleSheet, Text, View } from "react-native";
import MapView, { Marker } from "react-native-maps";
import { convertTimestampToDate, nowNs } from "stream-chat";
import { useChannelContext, useChatContext } from "stream-chat-react-native";
import type {
  ComponentOverrides,
  MessageLocationProps,
} from "stream-chat-react-native";

const MessageMap = ({ message }: MessageLocationProps) => {
  const { client } = useChatContext();
  const { channel } = useChannelContext();
  const location = message.shared_location;
  if (!location) return null;

  const { end_at, latitude, longitude, message_id, user_id } = location;
  const isLive = end_at != null && end_at > nowNs();
  const isOwn = user_id === client.userId;

  return (
    <View>
      <MapView
        region={{
          latitude,
          latitudeDelta: 0.02,
          longitude,
          longitudeDelta: 0.02,
        }}
        scrollEnabled={false}
        style={styles.map}
        zoomEnabled={false}
      >
        <Marker coordinate={{ latitude, longitude }} />
      </MapView>
      {end_at == null ? null : isLive && isOwn ? (
        <Pressable
          onPress={() => channel.stopLiveLocationSharing({ message_id })}
        >
          <Text style={styles.footer}>Stop sharing</Text>
        </Pressable>
      ) : (
        <Text style={styles.footer}>
          {isLive
            ? `Live until ${convertTimestampToDate(end_at)?.toLocaleTimeString()}`
            : "Location sharing ended"}
        </Text>
      )}
    </View>
  );
};

// Pass to `WithComponents`, declared once at module scope.
export const overrides: ComponentOverrides = { MessageLocation: MessageMap };

const styles = StyleSheet.create({
  footer: { padding: 8 },
  map: { height: 160, width: 240 },
});
```

`end_at` is a timestamp in nanoseconds, so compare it with `nowNs()`, and turn it into a `Date` with `convertTimestampToDate()`. A live location's card re-renders with each new position, so the pin follows. `messageContentOrder` on `Channel` places the location among the message's parts, after the attachments and before the text by default.

### Share from the attachment picker

A button of your own in the attachment picker's selection bar can share the position. This one shares it once, with `@react-native-community/geolocation`, and is left out in threads:

```tsx
import Geolocation from "@react-native-community/geolocation";
import { StyleSheet, View } from "react-native";
import type { MessageComposer } from "stream-chat";
import {
  AttachmentTypePickerButton,
  CameraPickerButton,
  FilePickerButton,
  MediaPickerButton,
  PollPickerButton,
  useComponentsContext,
  useMessageComposer,
  useThreadContext,
} from "stream-chat-react-native";
import type { ComponentOverrides } from "stream-chat-react-native";

const shareCurrentLocation = (composer: MessageComposer) =>
  Geolocation.getCurrentPosition(async ({ coords }) => {
    composer.locationComposer.setData({
      latitude: coords.latitude,
      longitude: coords.longitude,
    });
    try {
      await composer.sendLocation();
    } catch {
      // The composer has published `api:location:create:failed`.
    }
  });

const LocationPickerButton = () => {
  const { icons } = useComponentsContext();
  const messageComposer = useMessageComposer();
  const { thread } = useThreadContext();
  if (thread) return null;

  return (
    <AttachmentTypePickerButton
      Icon={icons.MapPin}
      onPress={() => shareCurrentLocation(messageComposer)}
    />
  );
};

const SelectionBarWithLocation = () => (
  <View style={styles.bar}>
    <MediaPickerButton />
    <CameraPickerButton />
    <FilePickerButton />
    <PollPickerButton />
    <LocationPickerButton />
  </View>
);

// Pass to `WithComponents`, declared once at module scope.
export const overrides: ComponentOverrides = {
  AttachmentPickerSelectionBar: SelectionBarWithLocation,
};

const styles = StyleSheet.create({
  bar: { alignItems: "center", flexDirection: "row", paddingHorizontal: 12 },
});
```

For a live location, pass `durationMs` to `LocationComposer.setData()`, for example from a dialog that offers the durations. The [Location sharing UI cookbook](https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/app/location-sharing/) builds that dialog, as a content of the attachment picker.

### Follow a live location on a map screen

`useHandleLiveLocationEvents({ channel, messageId, onLocationUpdate })` follows one message's location for a screen of its own, outside the message list. It returns the latest `locationResponse`, and `isLiveLocationStopped`, which turns `true` when an update arrives whose end time has passed. Until then it's `null`, so check `end_at` yourself for a share that ended before the screen opened. `onLocationUpdate`, called with each new location, can move the map.

To open that screen when the user taps a location, use `onPressMessage` on `Channel`: a tap on the message's content has the emitter `messageContent`, and the payload carries the `message` with its `shared_location`.

## Reference

- [Location hooks](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/location-hooks/): `LiveLocationManagerProvider`, `useLiveLocationManagerContext` and `useHandleLiveLocationEvents`.
- [Overriding components](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/overriding-components/): `MessageLocation` and the other optional slots.
- [MessageComposer API](https://getstream.io/chat/docs/sdk/react-native/v10/reference/state/messagecomposer-api/): `locationComposer` and `sendLocation()`.
- [Configuration tree](https://getstream.io/chat/docs/sdk/react-native/v10/reference/state/configuration-tree/): `messageComposer.location` and `liveLocationManager`.
- [Location sharing](https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/app/location-sharing/): the full UI cookbook, with permissions, the share dialog and a map screen.

---

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