/// Get a channel controller for the channel.
let channelController = chatClient.channelController(for: ChannelId(type: .messaging, id: "general"))
/// Send a message with a static location.
let location = LocationInfo(latitude: 10, longitude: 10)
channelController.sendStaticLocation(location)
/// Alternatively, you can use the create new message with a `NewLocationInfo` object and endAt as nil.
let newLocation = NewLocationInfo(latitude: 10, longitude: 10, endAt: nil)
let message = channelController.createNewMessage(text: "", location: newLocation)
Location Sharing
Location sharing allows users to send a static position or share their real-time location with other participants in a channel. Stream Chat supports both static and live location sharing through location attachments.
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.
The SDK handles location message creation and updates, but location tracking must be implemented by the application using device location services.
Sending static location
Static location sharing allows you to send a message containing a static location.
// Send a static location message.
chatClient.sendStaticLocation(
cid = "channelType:channelId",
latitude = -8.0421,
longitude = -34.9351,
deviceId = "my-device-id",
).enqueue { /* ... */ }
channel := chatClient.CreateChannelWithMembers(ctx, "messaging", channelID, userID)
// Create a SharedLocation Object
location := &SharedLocation{
Longitude: &longitude,
Latitude: &latitude,
CreatedByDeviceID: "test-device",
}
// Send a message with the SharedLocation object
message := channel.SendMessage(ctx, &Message{
ShareSharedLocation: location,
})
// Create shared location request
SharedLocationRequest locationRequest = new SharedLocation.SharedLocationRequest();
locationRequest.setCreatedByDeviceId(deviceId);
locationRequest.setLatitude(latitude);
locationRequest.setLongitude(longitude);
// Convert request to SharedLocation
SharedLocation sharedLocation = new SharedLocation();
sharedLocation.setCreatedByDeviceId(locationRequest.getCreatedByDeviceId());
sharedLocation.setLatitude(locationRequest.getLatitude());
sharedLocation.setLongitude(locationRequest.getLongitude());
// Send message with shared location
MessageRequestObject messageRequest =
MessageRequestObject.builder()
.sharedLocation(sharedLocation)
.build();
Message message =
Message.send(testChannel.getType(), testChannel.getId())
.message(messageRequest)
.request()
.getMessage();
@location_channel = @client.channel('messaging', channel_id: SecureRandom.uuid)
location = {
created_by_device_id: "test-device",
latitude: 40.7128,
longitude: -74.0060,
}
response = @location_channel.send_message({
shared_location: location
})
var channel = await CreateChannelAsync(createdByUserId: _user1.Id, members: new[] { _user1.Id });
// Create a shared location for the initial message
var location = new SharedLocationRequest
{
Longitude = longitude,
Latitude = latitude,
EndAt = null, // null for static location
CreatedByDeviceId = "test-device",
};
// Send a message with shared location
var messageRequest = new MessageRequest
{
Text = "Test message for shared location",
SharedLocation = location,
};
var messageResp = await _messageClient.SendMessageAsync(
channel.Type,
channel.Id,
messageRequest,
_user1.Id);
# Send a static location message
now = datetime.datetime.now(datetime.timezone.utc)
shared_location = {
"created_by_device_id": "test_device_id",
"latitude": 37.7749,
"longitude": -122.4194,
# No 'end_at' for static location
}
channel.send_message(
{"text": "Message with static location", "shared_location": shared_location},
user_id,
)
// Send a static location message
$sharedLocation = [
'created_by_device_id' => 'test_device_id',
'latitude' => 37.7749,
'longitude' => -122.4194,
// No 'end_at' for static location
];
$channel->sendMessage(
['text' => 'Message with static location', 'shared_location' => $sharedLocation],
$userId
);
Starting live location sharing
Live location sharing enables real-time location updates for a specified duration. The SDK manages the location message lifecycle, but your application is responsible for providing location updates.
/// Get a channel controller for the channel.
let channelController = chatClient.channelController(for: ChannelId(type: .messaging, id: "general"))
/// Send a message with a live location with the initial coordinates.
let location = LocationInfo(latitude: 10, longitude: 10)
channelController.startLiveLocationSharing(location)
/// Alternatively, you can use the create new message with a `NewLocationInfo` object and endAt non-nil.
let oneMinuteFromNow = Date().addingTimeInterval(60)
let newLocation = NewLocationInfo(latitude: 10, longitude: 10, endAt: oneMinuteFromNow)
let message = channelController.createNewMessage(text: "", location: newLocation)
// Start a live location sharing and automatically stops after 10 minutes.
val tenMinutesFromNow = Date().apply { time += 10.minutes.inWholeMilliseconds }
chatClient.startLiveLocationSharing(
cid = "channelType:channelId",
latitude = -8.0421,
longitude = -34.9351,
deviceId = "my-device-id",
endAt = tenMinutesFromNow,
).enqueue { /* ... */ }
channel := chatClient.CreateChannelWithMembers(ctx, "messaging", channelID, userID)
// Create a SharedLocation Object with end_at
end_at := time.Now().Add(1 * time.Hour)
location := &SharedLocation{
Longitude: &longitude,
Latitude: &latitude,
EndAt: &end_at,
CreatedByDeviceID: "test-device",
}
// Send a message with the SharedLocation object
message := channel.SendMessage(ctx, &Message{
SharedLocation: location,
})
// Create shared location request
SharedLocationRequest locationRequest = new SharedLocation.SharedLocationRequest();
locationRequest.setCreatedByDeviceId(deviceId);
locationRequest.setLatitude(latitude);
locationRequest.setLongitude(longitude);
locationRequest.setEndAt(end_at);
// Convert request to SharedLocation
SharedLocation sharedLocation = new SharedLocation();
sharedLocation.setCreatedByDeviceId(locationRequest.getCreatedByDeviceId());
sharedLocation.setLatitude(locationRequest.getLatitude());
sharedLocation.setLongitude(locationRequest.getLongitude());
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
sharedLocation.setEndAt(dateFormat.parse(locationRequest.getEndAt()));
// Send message with shared location
MessageRequestObject messageRequest =
MessageRequestObject.builder()
.sharedLocation(sharedLocation)
.build();
Message message =
Message.send(testChannel.getType(), testChannel.getId())
.message(messageRequest)
.request()
.getMessage();
@location_channel = @client.channel('messaging', channel_id: SecureRandom.uuid)
location = {
created_by_device_id: SecureRandom.uuid,
latitude: 40.7128,
longitude: -74.0060,
end_at: (Time.now + 3600).iso8601
}
response = @location_channel.send_message({
shared_location: location
})
var channel = await CreateChannelAsync(createdByUserId: _user1.Id, members: new[] { _user1.Id });
// Create a shared location for live location sharing (with EndAt)
var location = new SharedLocationRequest
{
Longitude = longitude,
Latitude = latitude,
EndAt = DateTimeOffset.UtcNow.AddHours(1), // Set duration for live location
CreatedByDeviceId = "test-device",
};
// Send a message with shared location
var messageRequest = new MessageRequest
{
Text = "Test message for live location sharing",
SharedLocation = location,
};
var messageResp = await _messageClient.SendMessageAsync(
channel.Type,
channel.Id,
messageRequest,
_user1.Id);
# Send a live location message (with end_at)
now = datetime.datetime.now(datetime.timezone.utc)
one_hour_later = now + datetime.timedelta(hours=1)
shared_location = {
"created_by_device_id": "test_device_id",
"latitude": 37.7749,
"longitude": -122.4194,
"end_at": one_hour_later.isoformat(),
}
channel.send_message(
{"text": "Message with live location", "shared_location": shared_location},
user_id,
)
// Send a live location message (with end_at)
$oneHourLater = (new DateTimeImmutable('now', new DateTimeZone('UTC')))->modify('+1 hour')->format(DateTime::ATOM);
$sharedLocation = [
'created_by_device_id' => 'test_device_id',
'latitude' => 37.7749,
'longitude' => -122.4194,
'end_at' => $oneHourLater,
];
$channel->sendMessage(
['shared_location' => $sharedLocation],
$userId
);
Stopping live location sharing
You can stop live location sharing for a specific message using the message controller:
/// Get the message controller for the live location message
let messageController = chatClient.messageController(
cid: channelId,
messageId: liveLocationMessageId
)
/// Stop live location sharing
messageController.stopLiveLocationSharing()
// Stop a live location sharing.
chatClient.stopLiveLocationSharing(
messageId = "live-location-message-id",
deviceId = "my-device-id",
).enqueue { /* ... */ }
// Set end_at to now
end_at := time.Now()
location := &SharedLocation{
MessageID: message_id,
Longitude: &longitude,
Latitude: &latitude,
EndAt: &end_at,
CreatedByDeviceID: "test-device",
}
// Update the live location
c.UpdateUserActiveLocation(ctx, user.ID, newLocation)
// Create SharedLocation request with time.now
SharedLocationRequest updatedLocationRequest = new SharedLocation.SharedLocationRequest();
updatedLocationRequest.setMessageId(initialMessage.getId());
updatedLocationRequest.setCreatedByDeviceId(deviceId);
updatedLocationRequest.setEndAt(LocalDate.now());
// Update the live location
SharedLocation.SharedLocationResponse updateResponse =
SharedLocation.updateLocation()
.userId(testUserRequestObject.getId())
.request(updatedLocationRequest)
.request();
location = {
message_id: message_id,
created_by_device_id: "test-device",
end_at: Time.now.iso8601
}
@client.update_location(location)
// Stop live location sharing by setting EndAt to now
var stopLocationRequest = new SharedLocationRequest
{
MessageId = liveLocationMessageId, // The ID of the live location message
Longitude = longitude,
Latitude = latitude,
EndAt = DateTimeOffset.UtcNow, // Set EndAt to now to stop sharing
CreatedByDeviceId = "test-device",
};
// Update the live location
var response = await UpdateLocationAsync(userID, stopLocationRequest);
# Update the user's live location (e.g., when device location changes)
location_data = {
"created_by_device_id": "test_device_id",
"latitude": new_latitude,
"longitude": new_longitude,
}
client.update_user_location(
user_id,
message_id,
location_data
)
// Update the user's live location (e.g., when device location changes)
$locationData = [
'created_by_device_id' => 'test_device_id',
'latitude' => $newLatitude,
'longitude' => $newLongitude,
];
$client->updateUserActiveLiveLocation(
$userId,
$messageId,
$locationData
);
Updating live location
Your application must implement location tracking and provide updates to the SDK. The SDK handles updating all the current user’s active live location messages and provides a throttling mechanism to prevent excessive API calls.
/// Get the current user controller.
let currentUserController = chatClient.currentUserController()
/// Set delegate to receive live location updates.
currentUserController.delegate = self
/// Load active live locations (call only once at app start or when chat is initialized)
currentUserController.loadActiveLiveLocationMessages()
/// Update location when device location changes.
func locationDidUpdate(_ newLocation: LocationInfo) {
currentUserController.updateLiveLocation(newLocation)
}
// Query user's active live locations (call only once when user is connected).
chatClient.queryActiveLocations().enqueue { /* ... */ }
// Listen for changes in current user's active live locations.
chatClient.globalStateFlow
.flatMapLatest { it.currentUserActiveLiveLocations }
.onEach { userActiveLiveLocations ->
if (userActiveLiveLocations.isEmpty()) {
// Stop receiving device location updates if there's no active live locations.
} else {
// Start receiving device location updates (check for location permissions).
}
}
.launchIn(coroutineScope)
// Update live location when device location changes.
for (deviceLocation in result.locations) {
userActiveLiveLocations
.filterNot { it.endAt?.before(Date()) ?: false } /// Filter out expired locations
.forEach { activeLiveLocation ->
chatClient.updateLiveLocation(
messageId = activeLiveLocation.messageId,
latitude = deviceLocation.latitude,
longitude = deviceLocation.longitude,
deviceId = "my-device-id",
).enqueue { /* ... */ }
}
}
// Get current user active live locations
userActiveLiveLocations, err := c.GetUserActiveLocations(ctx, user.ID)
// New location to set
end_at := time.Now()
newLocation := &SharedLocation{
Longitude: &longitude,
Latitude: &latitude,
EndAt: &end_at,
CreatedByDeviceID: "test-device",
}
// Update active live location of the current user
for _, location := range userActiveLiveLocations.ActiveLiveLocations {
newLocation.MessageID = location.MessageID
c.UpdateUserActiveLocation(ctx, user.ID, newLocation)
}
// Get active live locations
ActiveLiveLocationsResponse activeLocations =
SharedLocation.getLocations().userId(userRequestObject.getId()).request();
// Create updated location request
SharedLocationRequest updatedLocationRequest = new SharedLocation.SharedLocationRequest();
updatedLocationRequest.setMessageId(initialMessage.getId());
updatedLocationRequest.setCreatedByDeviceId(deviceId);
updatedLocationRequest.setEndAt(end_at);
// Update the live location
SharedLocation.SharedLocationResponse updateResponse =
SharedLocation.updateLocation()
.request(updatedLocationRequest)
.request();
location = {
message_id: message_id,
created_by_device_id: "test-device",
end_at: Time.now.iso8601
}
@client.update_location(location)
// Get all active live locations for the current user
var activeLocations = await GetSharedLocationsAsync(userID);
// New location to set (e.g., from device location update)
var newLocation = new SharedLocationRequest
{
Longitude = newLongitude, // updated longitude
Latitude = newLatitude, // updated latitude
EndAt = endAt, // keep the same end time
CreatedByDeviceId = "test-device",
};
// Update all active live locations for the user
foreach (var location in activeLocations.ActiveLiveLocations)
{
newLocation.MessageId = location.MessageId;
await UpdateLocationAsync(userID, newLocation);
}
# Update the user's live location (e.g., when device location changes)
location_data = {
"created_by_device_id": "test_device_id",
"latitude": new_latitude,
"longitude": new_longitude,
}
client.update_user_location(
user_id,
message_id,
location_data
)
// Update the user's live location (e.g., when device location changes)
$location = [
'latitude' => $newLatitude,
'longitude' => $newLongitude
];
$response = $client->updateUserActiveLiveLocation($userId, $messageId, $location);
Whenever the location is updated, the message will automatically be updated with the new location.
The SDK will also notify your application when it should start or stop location tracking as well as when the active live location messages change.
extension SomeObject: CurrentChatUserControllerDelegate {
/// Called when the user starts sharing a live location and it wasn't already sharing a live location
func currentUserControllerDidStartSharingLiveLocation(
_ controller: CurrentChatUserController
) {
// Start location monitoring (Needs to be implemented by the application)
startLocationTracking()
}
/// Called when all live location sharing stops
func currentUserControllerDidStopSharingLiveLocation(
_ controller: CurrentChatUserController
) {
// Stop location monitoring
stopLocationTracking()
}
/// Called when active live location messages change
func currentUserController(
_ controller: CurrentChatUserController,
didChangeActiveLiveLocationMessages messages: [ChatMessage]
) {
// You can use this delegate method to track the active live location messages and
// if needed render all of them in a UI component or you can use this for finer control
// when to start or stop tracking locations.
}
/// Called when a live location update fails
func currentUserController(
_ controller: CurrentChatUserController,
didFailToUpdateLiveLocation location: SharedLocation,
with error: Error
) {
// Handle error if needed
}
}
Events
Whenever a location is created or updated, the following WebSocket events will be sent:
message.new
: When a new location message is created.message.updated
: When a location message is updated.
You can easily check if a message is a location message by checking the message.sharedLocation
property. For example, you can use this events to render the locations in a map view.
let eventsController = ChatClient.shared.eventsController()
eventsController.delegate = self
extension CustomMapView: EventsControllerDelegate {
func eventsController(_ controller: EventsController, didReceiveEvent event: any Event) {
/// Make sure the event is for the current channel.
guard event.cid == currentChannelId else {
return
}
if let event = event as? MessageNewEvent, let sharedLocation = event.message.sharedLocation {
// Add the new location to the map.
} else if let event = event as? MessageUpdatedEvent, let sharedLocation = event.message.sharedLocation { {
// Update the location on the map.
}
}
}
chatClient.subscribeFor(NewMessageEvent::class, MessageUpdatedEvent::class) { event ->
/// Check if the event is for the watching channel.
if ((event as? CidEvent)?.cid == "my-watching-cid") {
when (event) {
is NewMessageEvent -> event.message.sharedLocation?.let { location ->
// Add a new location to the map.
}
is MessageUpdatedEvent -> event.message.sharedLocation?.let { location ->
// Update the existing location in the map.
}
else -> Unit
}
}
}