Location Sharing

Location sharing is a powerful feature that allows users to share their current position or real-time location with other participants in a channel. Stream Chat supports both static and live location sharing.

There are two types of location sharing:

  • Static Location: A one-time location share that does not update over time.
  • Live Location: A real-time location share that updates over time for a specified duration.

The SDK handles location message creation and updates, but location tracking must be implemented by the application using device location services.

Adding Live Location Sharing

In this guide, we will be adding location sharing functionality to a React Native chat app, similar to WhatsApp, iMessage, or Telegram. Other location sharing use cases, like delivery tracking, can be implemented in a similar way. The Stream Chat SDK provides the underlying functionality for location sharing, but, at the moment, does not include default UI components. However, we provide a complete example implementation in our Sample App/Expo Messaging app that you can reference and adapt for your needs.

Setup

Install Dependencies

Run the following commands in your terminal of choice:

yarn add @react-native-community/geolocation
yarn add react-native-maps
  • @react-native-community/geolocation library is used to watch the current location of the user and then send it in the message
  • react-native-maps is used to display the location of the user using the native maps present on iOS and Android

Configure location permissions

To configure location permissions, you need to follow the platform-specific instructions below:

iOS

You need to include NSLocationWhenInUseUsageDescription and NSLocationAlwaysAndWhenInUseUsageDescription in Info.plist to enable geolocation when using the app.

Android

To request access to location, you need to add the following line to your app’s AndroidManifest.xml:

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

Android: Add API Key

On Android, one has to use Google Maps, which in turn requires you to obtain an API key for the Android SDK. On iOS, the native Apple Maps implementation is used and API keys are not necessary.

Add your API key to your manifest file (android/app/src/main/AndroidManifest.xml):

<application>
   <!-- You will only need to add this meta-data tag, but make sure it's a child of application -->
   <meta-data
     android:name="com.google.android.geo.API_KEY"
     android:value="Your Google maps API Key Here"/>
</application>

Implementation outline

We will create the following components/screens to implement static/live location sharing:

  • A modal that allows users to share their static or live location.
  • A component that displays the location on a map in the MessageList.
  • Opening the map in a full-screen view when the user taps on the location message.
  • Ability to stop sharing the live location and update the message with the end time.

Creating a static/live location message

To create a static message with a location, you can use the locationComposer from the messageComposer to set the Data and then call sendLocation method to send the message:

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

To create a live location message, you can use the locationComposer from the messageComposer to set the Data and then call sendLocation method to send the message. The locationComposer will automatically start watching the location and update the message with the new location.

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

Preview of create location modal

An example implementation using the Geolocation watchPosition method to send the static or live location message can be found here:

To add the location sharing button, you can customize the InputButtons component in the Channel component. You can add a button that opens the modal to share the location.

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

const InputButtons: NonNullable<
  React.ComponentProps<typeof Channel>["InputButtons"]
> = (props) => {
  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;

Showing the shared location in the message

If a location is shared in a message, you would ideally get the shared_location field in the message object. This field contains the latitude and longitude of the location shared and other details. The SDK does not provide a default UI for displaying the location, so you will need to create a custom component to display the location on a map. You can pass that component to the MessageLocation component in the Channel component.

To display the location on a map, you can use the react-native-maps library. You can create a custom component that takes the latitude and longitude as props and displays the location on a map.

Preview of message location

An example implementation of custom MessageLocation can be found here:

Globally watching the location using the manager

An application can have multiple shared live location messages across channels. All the active live locations for a client/user can be queried through the client.getSharedLocations(). This method returns a list of all active_live_locations for the user. The active_live_locations is a list of all the live locations that are currently being shared by the user.

To manage the live location sharing, we use the LiveLocationManager class from the stream-chat package. This manager allows you to watch the live location sharing for a specific message and update it in real-time.

The SDK exposes a context wrapper that can be wrapped around the app to provide the LiveLocationManager instance to the components. It handles the register/deregistration of the events necessary to manage the live location sharing.

To wrap the LiveLocationManager around the app, you can use the LiveLocationManagerProvider component from the stream-chat-react-native package. This component provides the LiveLocationManager instance to the components that need it. This expects a watchLocation that will be used to watch the location from the device.

An example implementation of the watchLocation can be found here:

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);
    }
  };
};

Finally, this is how it can be used in the app:

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

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

This, optionally also expects a getDeviceId function(that returns a string) to be passed in as a prop, which is used to identify the device for which the live location is being shared.

To view the live location on a full-screen map, you can navigate to a detail screen when the user taps on the location message. You can use the onPressMessage prop of the Channel component to handle the tap event on the message.

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>;
};

Add the map details screen

In this screen, we display the coordinates of the map in a full screen map. Additionally we also show the button to stop sharing the location and the timestamp of when the location sharing ended/will be ended.

Current LocationLive LocationLive Location Ended
Preview of chat screen
Preview of chat screen
Preview of chat screen

For this screen, to work we need to listen to a few events from the backend so that we can update the state accordingly.

One of them being:

  • message.updated - Received when the message’s shared_location changes.

To listen to the updates in the screen you can use the hook useHandleLiveLocationEvents. This expects the messageId, channel and the onLocationUpdate function as parameters.

The messageId, channel are self-explanatory. The onLocationUpdate function is the one that would be triggered inside the listener of the message.updated event. This can be particularly used to update the marker on the map view.

An example implementation of custom MapScreen can be found here:

You should now be able to see the following screen with a detailed map:

Stopping the live location sharing

To stop the live location sharing, you can use the stopLiveLocationSharing function from the client. It expects the current location response as argument.

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

Sample code

This cookbook is available as a full fledged app sample on our sample apps.

© Getstream.io, Inc. All Rights Reserved.