# Location Sharing

## Introduction

Stream's Chat SDK allows users to share their location with other members of a channel.

This guide demonstrates how to implement a [location-sharing feature](/chat/docs/android/location_sharing/)
where users can send their location to a chat channel and display it using a custom attachment component with maps.

| Location Picker                                                                              | Static Location                                                                              | Live Location                                                                            |
| -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| ![Location Picker](@chat-sdk/android/v6-latest/_assets/location-sharing/location-picker.png) | ![Static Location](@chat-sdk/android/v6-latest/_assets/location-sharing/static-location.png) | ![Live Location](@chat-sdk/android/v6-latest/_assets/location-sharing/live-location.png) |

The implementation uses Jetpack Compose for the UI and the [Stream Chat Android SDK](/chat/docs/sdk/android/) for chat functionality.

## Prerequisites

- Stream Chat SDK: Ensure you have integrated the Stream Chat Compose SDK into your project. Follow the [Compose In-App Messaging Tutorial](https://getstream.io/chat/compose/tutorial/) for setup instructions.
- Permissions: Add location permissions to your app.
- Dependencies: Include the necessary dependencies for location services.

## 1: Set Up Dependencies

Add the following dependencies to your app/build.gradle file to enable Stream Chat and location services:

```kotlin
dependencies {
    // Stream Chat SDK
    implementation("io.getstream:stream-chat-android-compose:$stream_version")
    implementation("io.getstream:stream-chat-android-offline:$stream_version")

    // Google Play Services for location
    implementation("com.google.android.gms:play-services-location:$play_services_version")
}
```

Replace `$stream_version` with the latest version from the [Stream Chat Android GitHub releases](https://github.com/GetStream/stream-chat-android/releases).

Sync your project to ensure dependencies are resolved.

## 2: Request Location Permissions

To access the user’s location, add the following permissions to your `AndroidManifest.xml`:

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

<admonition type="tip">

Ensure permissions are requested before accessing location services.
Check out more about location permissions in the [Android documentation](https://developer.android.com/training/location/permissions).

</admonition>

## 3: Send Device Location Updates

To send device location updates, create a simple class that handles location updates and sends them to the chat channel.
This class will use the `FusedLocationProviderClient` to get location updates and the `ChatClient.updateLiveLocation` to update a started live location message.

```kotlin
/**
 * Consider using a foreground service for continuous location updates while the app is in the background.
 */
class SharedLocationService(private val context: Context) : LocationCallback() {

    private val locationClient = LocationServices.getFusedLocationProviderClient(context)

    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    private val chatClient by lazy { ChatClient.instance() }

    @Volatile
    private var activeLiveLocations: List<Location> = emptyList()

    // Track whether we are currently receiving updates
    @Volatile
    private var isReceivingUpdates = false

    var currentDeviceId: String = UnknownDeviceId
        private set

    // Call this when the user connects to the client
    fun start() {
        scope.launch { currentDeviceId = /* Get current device if logic */ }

        // Fetch user's active live locations
        chatClient.queryActiveLocations().enqueue()

        // Listen for changes in current user's active live locations
        chatClient.globalStateFlow
            .flatMapLatest { it.currentUserActiveLiveLocations }
            .onEach { userActiveLiveLocations ->

                activeLiveLocations = userActiveLiveLocations.toList()

                if (userActiveLiveLocations.isEmpty()) {
                    // No active locations, stop receiving updates
                    locationClient.removeLocationUpdates(this)
                    isReceivingUpdates = false
                } else {
                    if (!isReceivingUpdates) {
                        // If we are not already receiving updates, request location updates
                        val hasPermission = ContextCompat.checkSelfPermission(
                            context,
                            Manifest.permission.ACCESS_FINE_LOCATION,
                        ) == PackageManager.PERMISSION_GRANTED
                        if (hasPermission) {
                            val request = LocationRequest.Builder(
                                Priority.PRIORITY_HIGH_ACCURACY,
                                5000L, // Location updates internal in millis
                            ).build()
                            locationClient.requestLocationUpdates(request, this, Looper.getMainLooper())
                            isReceivingUpdates = true
                        } else {
                            // Log or handle the case where location permission is not granted
                        }
                    } else {
                        // Already receiving updates, no need to request again
                    }
                }
            }
            .launchIn(scope)
    }

    override fun onLocationResult(result: LocationResult) {

        val locationsToUpdate = activeLiveLocations

        // Check if we have any active live locations to update
        for (deviceLocation in result.locations) {
            locationsToUpdate
                .filterNot { it.endAt?.before(Date()) ?: false } // Filter out expired locations
                .forEach { activeLiveLocation ->
                    chatClient.updateLiveLocation(
                        messageId = activeLiveLocation.messageId,
                        latitude = deviceLocation.latitude,
                        longitude = deviceLocation.longitude,
                        deviceId = currentDeviceId,
                    ).enqueue { /* Handle success or error */ }
                }
        }
    }

    // Call this when the user disconnects from the client
    fun stop() {
        // Stop receiving location updates
        locationClient.removeLocationUpdates(this)
        scope.cancel()
    }
}
```

Check out [SharedLocationService](https://github.com/GetStream/stream-chat-android/blob/develop/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/service/SharedLocationService.kt) for a complete implementation.

## 4: Add Location Picker

To allow users to select a location, you can create a location picker dialog or screen.
This example uses a [custom attachment picker tab](https://getstream.io/chat/docs/sdk/android/compose/message-components/attachments-picker/#providing-a-custom-tab).

```kotlin
internal class LocationPickerTabFactory(
    private val viewModelFactory: SharedLocationViewModelFactory,
) : AttachmentsPickerTabFactory {

    override val attachmentsPickerMode: AttachmentsPickerMode =
        CustomPickerMode()

    override fun isPickerTabEnabled(channel: Channel): Boolean =
        channel.config.sharedLocationsEnabled

    @Composable
    override fun PickerTabIcon(isEnabled: Boolean, isSelected: Boolean) {
        Icon(
            imageVector = Icons.Rounded.ShareLocation,
            contentDescription = "Share Location",
            tint = when {
                isEnabled -> ChatTheme.colors.textLowEmphasis
                else -> ChatTheme.colors.disabled
            },
        )
    }

    @Composable
    override fun PickerTabContent(
        onAttachmentPickerAction: (AttachmentPickerAction) -> Unit,
        attachments: List<AttachmentPickerItemState>,
        onAttachmentsChanged: (List<AttachmentPickerItemState>) -> Unit,
        onAttachmentItemSelected: (AttachmentPickerItemState) -> Unit,
        onAttachmentsSubmitted: (List<AttachmentMetaData>) -> Unit,
    ) {
        LocationPicker(
            viewModelFactory = viewModelFactory,
            onDismiss = { onAttachmentPickerAction(AttachmentPickerBack) },
        )
    }
}
```

Then set up the `LocationPickerTabFactory` in the `ChatTheme`:

```kotlin
val attachmentsPickerTabFactories = AttachmentsPickerTabFactories.defaultFactories() +
    LocationPickerTabFactory(viewModelFactory = SharedLocationViewModelFactory(cid))
ChatTheme(
    attachmentsPickerTabFactories = attachmentsPickerTabFactories,
)
```

<admonition type="note">

`LocationPicker` uses `SharedLocationViewModel` to handle the following:

- Send a static location through the `ChatClient.sendStaticLocation` function when the user clicks the "Send Current Location" button.
- Start a live location through the `ChatClient.startLiveLocationSharing` function when the user clicks the "Share Live Location" button.

</admonition>

<admonition type="info">
  
`LocationPicker` renders a map using [Leaflet.js](https://leafletjs.com/) through a `WebView`.

</admonition>

Check out [LocationPickerTabFactory](https://github.com/GetStream/stream-chat-android/blob/develop/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/location/LocationPickerTabFactory.kt),
[LocationPicker](https://github.com/GetStream/stream-chat-android/blob/develop/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/location/LocationPicker.kt), and
[SharedLocationViewModel](https://github.com/GetStream/stream-chat-android/blob/develop/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/vm/SharedLocationViewModel.kt) for a complete implementation.

## 5: Display Shared Locations

To display shared locations in the chat, you can override the `MessageItemCenterContent` component factory and create a custom component that renders the location on a map.

```kotlin
class CustomChatComponentFactory(
    private val delegate: ChatComponentFactory = object : ChatComponentFactory {},
) : ChatComponentFactory by deltegate {

    @Composable
    override fun ColumnScope.MessageItemCenterContent(
        messageItem: MessageItemState,
        onLongItemClick: (Message) -> Unit,
        onPollUpdated: (Message, Poll) -> Unit,
        onCastVote: (Message, Poll, Option) -> Unit,
        onRemoveVote: (Message, Poll, Vote) -> Unit,
        selectPoll: (Message, Poll, PollSelectionType) -> Unit,
        onAddAnswer: (message: Message, poll: Poll, answer: String) -> Unit,
        onClosePoll: (String) -> Unit,
        onAddPollOption: (poll: Poll, option: String) -> Unit,
        onGiphyActionClick: (GiphyAction) -> Unit,
        onQuotedMessageClick: (Message) -> Unit,
        onLinkClick: ((Message, String) -> Unit)?,
        onUserMentionClick: (User) -> Unit,
        onMediaGalleryPreviewResult: (MediaGalleryPreviewResult?) -> Unit,
    ) {
        val message = messageItem.message
        // Check if the message has a shared location and is not deleted
        if (message.hasSharedLocation() && !message.isDeleted()) {
            val location = requireNotNull(message.sharedLocation)
            SharedLocationItem(
                modifier = Modifier.widthIn(max = ChatTheme.dimens.messageItemMaxWidth),
                message = message,
                location = location,
                onMapClick = { url -> onLinkClick?.invoke(message, url) },
                onMapLongClick = { onLongItemClick(message) },
            )
        } else {
            // Fallback to the default implementation
            with(delegate) {
                MessageItemCenterContent(
                    messageItem = messageItem,
                    onLongItemClick = onLongItemClick,
                    onGiphyActionClick = onGiphyActionClick,
                    onQuotedMessageClick = onQuotedMessageClick,
                    onLinkClick = onLinkClick,
                    onUserMentionClick = onUserMentionClick,
                    onMediaGalleryPreviewResult = onMediaGalleryPreviewResult,
                    onPollUpdated = onPollUpdated,
                    onCastVote = onCastVote,
                    onRemoveVote = onRemoveVote,
                    selectPoll = selectPoll,
                    onAddAnswer = onAddAnswer,
                    onClosePoll = onClosePoll,
                    onAddPollOption = onAddPollOption,
                )
            }
        }
    }
}
```

<admonition type="note">

When the shared location has an `endAt` date, it is considered a live location sharing.

In this guide, stoping a live location sharing is handled in the `SharedLocationViewModel` by calling the `ChatClient.stopLiveLocationSharing` function.

</admonition>

Then set up the `CustomChatComponentFactory` in the `ChatTheme`:

```kotlin
val chatComponentFactory = CustomChatComponentFactory()
ChatTheme(
    chatComponentFactory = chatComponentFactory,
)
```

<admonition type="info">

Learn more about [Component Factory](/chat/docs/sdk/android/compose/general-customization/component-factory/).

</admonition>

Check out [SharedLocationItem](https://github.com/GetStream/stream-chat-android/blob/develop/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/component/SharedLocationItem.kt) for a complete implementation.

## Summary

In this guide, you learned how to implement a location-sharing feature in your Stream Chat application using Jetpack Compose.
You set up dependencies, requested location permissions, sent device location updates, added a location picker, and displayed shared locations in the chat.

---

This page was last updated at 2025-08-15T14:15:40.367Z.

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