yarn add @react-native-community/geolocation
yarn add react-native-maps
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:
@react-native-community/geolocation
library is used to watch the current location of the user and then send it in the messagereact-native-maps
is used to display the location of the user using the native maps present on iOS and Android
yarn add expo-location
yarn add react-native-maps
expo-location
library is used to watch the current location of the user and then send it in the messagereact-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>
Configure the location permissions in your app.json
or app.config.js
file:
{
"expo": {
"plugins": [
[
"expo-location",
{
"locationAlwaysAndWhenInUsePermission": "Allow $(PRODUCT_NAME) to use your location."
}
]
]
}
}
Alternatively, you can follow this guide
.
Android: Add API Key
To set up the google maps API key, you can add the following to your app.json
or app.config.js
file:
{
"expo": {
"android": {
"config": {
"googleMaps": {
"apiKey": "Your Google maps API Key Here"
}
}
}
}
}
Alternatively, you can follow this guide
.
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();
};
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.
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);
}
};
};
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();
};
};
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.
Navigating from the message to the map detail screen
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>;
};
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) {
// Create url params from 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>;
};
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 Location | Live Location | Live Location Ended |
---|---|---|
![]() | ![]() | ![]() |
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’sshared_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.